this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

copy relay dashboard to per-implementation folder

+5597 -1
+1 -1
cmd/rerelay/Dockerfile
··· 19 19 20 20 WORKDIR /app 21 21 22 - COPY ts/bgs-dash /app/ 22 + COPY cmd/rerelay/relay-admin-ui /app/ 23 23 24 24 RUN yarn install --frozen-lockfile 25 25
+2
cmd/rerelay/relay-admin-ui/.gitignore
··· 1 + dist/ 2 + node_modules/
+13
cmd/rerelay/relay-admin-ui/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8" /> 5 + <!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> --> 6 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 + <title>Relay Dashboard</title> 8 + </head> 9 + <body> 10 + <div id="root"></div> 11 + <script type="module" src="src/main.tsx"></script> 12 + </body> 13 + </html>
+35
cmd/rerelay/relay-admin-ui/package.json
··· 1 + { 2 + "name": "relay-admin-ui", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "tsc && vite build", 9 + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 + "preview": "vite preview" 11 + }, 12 + "dependencies": { 13 + "@headlessui/react": "^1.7.15", 14 + "@heroicons/react": "^2.0.18", 15 + "@tailwindcss/forms": "^0.5.4", 16 + "react": "^18.2.0", 17 + "react-dom": "^18.2.0", 18 + "react-router-dom": "^6.14.2" 19 + }, 20 + "devDependencies": { 21 + "@types/react": "^18.2.14", 22 + "@types/react-dom": "^18.2.6", 23 + "@typescript-eslint/eslint-plugin": "^5.61.0", 24 + "@typescript-eslint/parser": "^5.61.0", 25 + "@vitejs/plugin-react": "^4.0.1", 26 + "autoprefixer": "^10.4.14", 27 + "eslint": "^8.44.0", 28 + "eslint-plugin-react-hooks": "^4.6.0", 29 + "eslint-plugin-react-refresh": "^0.4.1", 30 + "postcss": "^8.4.26", 31 + "tailwindcss": "^3.3.3", 32 + "typescript": "^5.0.2", 33 + "vite": "^4.4.0" 34 + } 35 + }
+6
cmd/rerelay/relay-admin-ui/postcss.config.js
··· 1 + export default { 2 + plugins: { 3 + tailwindcss: {}, 4 + autoprefixer: {}, 5 + }, 6 + }
+1
cmd/rerelay/relay-admin-ui/public/vite.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
+25
cmd/rerelay/relay-admin-ui/src/App.css
··· 1 + .fade-in { 2 + animation: fadeIn 0.5s; 3 + } 4 + 5 + .fade-out { 6 + animation: fadeOut 0.5s; 7 + } 8 + 9 + @keyframes fadeIn { 10 + from { 11 + opacity: 0; 12 + } 13 + to { 14 + opacity: 1; 15 + } 16 + } 17 + 18 + @keyframes fadeOut { 19 + from { 20 + opacity: 1; 21 + } 22 + to { 23 + opacity: 0; 24 + } 25 + }
+271
cmd/rerelay/relay-admin-ui/src/App.tsx
··· 1 + import "./App.css"; 2 + import { 3 + NavLink, 4 + RouterProvider, 5 + createBrowserRouter, 6 + useNavigate, 7 + } from "react-router-dom"; 8 + import Dash from "./components/Dash/Dash"; 9 + import { Disclosure } from "@headlessui/react"; 10 + import { Bars3Icon, XMarkIcon } from "@heroicons/react/24/outline"; 11 + import Login from "./components/Login/Login"; 12 + import { useEffect } from "react"; 13 + import Logout from "./components/Logout/Logout"; 14 + import Domains from "./components/Domains/Domains"; 15 + import Repos from "./components/Repos/Repos"; 16 + import Consumers from "./components/Consumers/Consumers"; 17 + import NewPDS from "./components/NewPDS/NewPDS"; 18 + 19 + function classNames(...classes: string[]) { 20 + return classes.filter(Boolean).join(" "); 21 + } 22 + 23 + // Redirect to /login if not authenticated 24 + function RequireAuth({ children }: { children: React.ReactNode }) { 25 + const navigate = useNavigate(); 26 + 27 + useEffect(() => { 28 + if (!localStorage.getItem("admin_route_token")) { 29 + navigate("/login"); 30 + } 31 + }, []); 32 + 33 + return children; 34 + } 35 + 36 + interface Route { 37 + path: string; 38 + name: string; 39 + element: React.ReactNode; 40 + requrieAuth?: boolean; 41 + hideIfAuth?: boolean; 42 + } 43 + 44 + const routes: Route[] = [ 45 + { 46 + path: "/", 47 + name: "PDS List", 48 + element: ( 49 + <RequireAuth> 50 + <Nav /> 51 + <main> 52 + <div className="mx-auto max-w-screen px-2 py-6 sm:px-6 lg:px-8"> 53 + <Dash /> 54 + </div> 55 + </main> 56 + </RequireAuth> 57 + ), 58 + requrieAuth: true, 59 + }, 60 + { 61 + path: "/new_pds", 62 + name: "New PDS", 63 + element: ( 64 + <RequireAuth> 65 + <Nav /> 66 + <main> 67 + <div className="mx-auto max-w-7xl px-2 py-6 sm:px-6 lg:px-8"> 68 + <NewPDS /> 69 + </div> 70 + </main> 71 + </RequireAuth> 72 + ), 73 + }, 74 + { 75 + path: "/consumers", 76 + name: "Consumers", 77 + element: ( 78 + <RequireAuth> 79 + <Nav /> 80 + <main> 81 + <div className="mx-auto max-w-7xl px-2 py-6 sm:px-6 lg:px-8"> 82 + <Consumers /> 83 + </div> 84 + </main> 85 + </RequireAuth> 86 + ), 87 + requrieAuth: true, 88 + }, 89 + { 90 + path: "/domain_bans", 91 + name: "Domain Bans", 92 + element: ( 93 + <RequireAuth> 94 + <Nav /> 95 + <main> 96 + <div className="mx-auto max-w-7xl px-2 py-6 sm:px-6 lg:px-8"> 97 + <Domains /> 98 + </div> 99 + </main> 100 + </RequireAuth> 101 + ), 102 + requrieAuth: true, 103 + }, 104 + { 105 + path: "/repo_takedowns", 106 + name: "Repo Takedowns", 107 + element: ( 108 + <RequireAuth> 109 + <Nav /> 110 + <main> 111 + <div className="mx-auto max-w-7xl px-2 py-6 sm:px-6 lg:px-8"> 112 + <Repos /> 113 + </div> 114 + </main> 115 + </RequireAuth> 116 + ), 117 + requrieAuth: true, 118 + }, 119 + 120 + { 121 + path: "/login", 122 + name: "Login", 123 + element: ( 124 + <> 125 + <Nav /> 126 + <main> 127 + <div className="mx-auto max-w-7xl px-2 py-6 sm:px-6 lg:px-8"> 128 + <Login /> 129 + </div> 130 + </main> 131 + </> 132 + ), 133 + requrieAuth: false, 134 + hideIfAuth: true, 135 + }, 136 + { 137 + path: "/logout", 138 + name: "Logout", 139 + element: ( 140 + <> 141 + <Nav /> 142 + <main> 143 + <div className="mx-auto max-w-7xl py-6 px-2 sm:px-6 lg:px-8"> 144 + <Logout /> 145 + </div> 146 + </main> 147 + </> 148 + ), 149 + requrieAuth: true, 150 + }, 151 + ]; 152 + 153 + const router = createBrowserRouter(routes, { 154 + basename: "/dash", 155 + }); 156 + 157 + function Nav() { 158 + const isAuthed = !!localStorage.getItem("admin_route_token"); 159 + return ( 160 + <Disclosure as="nav" className="bg-gray-800"> 161 + {({ open }) => ( 162 + <> 163 + <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"> 164 + <div className="flex h-16 items-center justify-between"> 165 + <div className="flex items-center"> 166 + <div className="flex-shrink-0"> 167 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" className="w-8 h-8 text-white"> 168 + <path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" /> 169 + </svg> 170 + </div> 171 + <div className="hidden md:block"> 172 + <div className="ml-10 flex items-baseline space-x-4"> 173 + {routes.map((item) => 174 + (isAuthed && item.hideIfAuth) || 175 + (!isAuthed && item.requrieAuth) ? null : ( 176 + <NavLink 177 + key={item.path} 178 + to={item.path || "/"} 179 + className={({ isActive }) => 180 + classNames( 181 + isActive 182 + ? "bg-gray-900 text-white" 183 + : "text-gray-300 hover:bg-gray-700 hover:text-white", 184 + "rounded-md px-3 py-2 text-sm font-medium" 185 + ) 186 + } 187 + aria-current={ 188 + router.state.location.pathname === item.path 189 + ? "page" 190 + : undefined 191 + } 192 + > 193 + {item.name} 194 + </NavLink> 195 + ) 196 + )} 197 + </div> 198 + </div> 199 + </div> 200 + <div className="hidden md:block"> 201 + <div className="ml-4 flex items-center md:ml-6"></div> 202 + </div> 203 + <div className="-mr-2 flex md:hidden"> 204 + {/* Mobile menu button */} 205 + <Disclosure.Button className="inline-flex items-center justify-center rounded-md bg-gray-800 p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"> 206 + <span className="sr-only">Open main menu</span> 207 + {open ? ( 208 + <XMarkIcon className="block h-6 w-6" aria-hidden="true" /> 209 + ) : ( 210 + <Bars3Icon className="block h-6 w-6" aria-hidden="true" /> 211 + )} 212 + </Disclosure.Button> 213 + </div> 214 + </div> 215 + </div> 216 + 217 + <Disclosure.Panel className="md:hidden"> 218 + <div className="space-y-1 px-2 pb-3 pt-2 sm:px-3"> 219 + {routes.map((item) => 220 + (isAuthed && item.hideIfAuth) || 221 + (!isAuthed && item.requrieAuth) ? null : ( 222 + <Disclosure.Button 223 + key={item.path} 224 + className={classNames( 225 + router.state.location.pathname === item.path 226 + ? "bg-gray-900 text-white" 227 + : "text-gray-300 hover:bg-gray-700 hover:text-white", 228 + "block rounded-md px-3 py-2 text-base font-medium" 229 + )} 230 + > 231 + <NavLink 232 + key={item.path} 233 + to={item.path || "/"} 234 + className={({ isActive }) => 235 + classNames( 236 + isActive 237 + ? "bg-gray-900 text-white" 238 + : "text-gray-300 hover:bg-gray-700 hover:text-white", 239 + "rounded-md px-3 py-2 text-sm font-medium" 240 + ) 241 + } 242 + aria-current={ 243 + router.state.location.pathname === item.path 244 + ? "page" 245 + : undefined 246 + } 247 + > 248 + {item.name} 249 + </NavLink> 250 + </Disclosure.Button> 251 + ) 252 + )} 253 + </div> 254 + </Disclosure.Panel> 255 + </> 256 + )} 257 + </Disclosure> 258 + ); 259 + } 260 + 261 + function App() { 262 + return ( 263 + <> 264 + <div className="min-h-full"> 265 + <RouterProvider router={router} /> 266 + </div> 267 + </> 268 + ); 269 + } 270 + 271 + export default App;
+1
cmd/rerelay/relay-admin-ui/src/assets/react.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
+347
cmd/rerelay/relay-admin-ui/src/components/Consumers/Consumers.tsx
··· 1 + import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/solid"; 2 + import { FC, useEffect, useState } from "react"; 3 + import Notification, { 4 + NotificationMeta, 5 + NotificationType, 6 + } from "../Notification/Notification"; 7 + 8 + import { RELAY_HOST } from "../../constants"; 9 + 10 + import { useNavigate } from "react-router-dom"; 11 + import { Consumer, ConsumerKey, ConsumerResponse } from "../../models/consumer"; 12 + 13 + const Consumers: FC<{}> = () => { 14 + const [consumerList, setConsumerList] = useState<Consumer[] | null>(null); 15 + const [sortField, setSortField] = useState<ConsumerKey>("ID"); 16 + const [sortOrder, setSortOrder] = useState<string>("asc"); 17 + 18 + // Notification Management 19 + const [shouldShowNotification, setShouldShowNotification] = 20 + useState<boolean>(false); 21 + const [notification, setNotification] = useState<NotificationMeta>({ 22 + message: "", 23 + alertType: "", 24 + }); 25 + 26 + const [adminToken, setAdminToken] = useState<string>( 27 + localStorage.getItem("admin_route_token") || "" 28 + ); 29 + const navigate = useNavigate(); 30 + 31 + const setAlertWithTimeout = ( 32 + type: NotificationType, 33 + message: string, 34 + dismiss: boolean 35 + ) => { 36 + setNotification({ 37 + message, 38 + alertType: type, 39 + autodismiss: dismiss, 40 + }); 41 + setShouldShowNotification(true); 42 + }; 43 + 44 + useEffect(() => { 45 + const token = localStorage.getItem("admin_route_token"); 46 + if (token) { 47 + setAdminToken(token); 48 + } else { 49 + navigate("/login"); 50 + } 51 + }, []); 52 + 53 + const refreshPDSList = () => { 54 + fetch(`${RELAY_HOST}/admin/consumers/list`, { 55 + method: "GET", 56 + headers: { 57 + "Content-Type": "application/json", 58 + Authorization: `Bearer ${adminToken}`, 59 + }, 60 + }) 61 + .then((res) => res.json()) 62 + .then((res: ConsumerResponse[]) => { 63 + if ("error" in res) { 64 + setAlertWithTimeout( 65 + "failure", 66 + `Failed to fetch Consumer list: ${res.error}`, 67 + true 68 + ); 69 + return; 70 + } 71 + const list: Consumer[] = res.map((consumer) => { 72 + return { 73 + RemoteAddr: consumer.remote_addr, 74 + UserAgent: consumer.user_agent, 75 + ID: consumer.id, 76 + EventsConsumed: consumer.events_consumed, 77 + ConnectedAt: new Date(Date.parse(consumer.connected_at)), 78 + }; 79 + }); 80 + 81 + const sortedList = sortConsumerList(list); 82 + setConsumerList(sortedList); 83 + }) 84 + .catch((err) => { 85 + setAlertWithTimeout( 86 + "failure", 87 + `Failed to fetch Consumer list: ${err}`, 88 + true 89 + ); 90 + }); 91 + }; 92 + 93 + const sortConsumerList = (list: Consumer[]): Consumer[] => { 94 + const sortedConsumers: Consumer[] = [...list].sort((a, b) => { 95 + if (sortOrder === "asc") { 96 + if (a[sortField]! < b[sortField]!) { 97 + return -1; 98 + } 99 + if (a[sortField]! > b[sortField]!) { 100 + return 1; 101 + } 102 + } else { 103 + if (a[sortField]! < b[sortField]!) { 104 + return 1; 105 + } 106 + if (a[sortField]! > b[sortField]!) { 107 + return -1; 108 + } 109 + } 110 + return 0; 111 + }); 112 + return sortedConsumers; 113 + }; 114 + 115 + useEffect(() => { 116 + if (!consumerList) { 117 + return; 118 + } 119 + setConsumerList(sortConsumerList(consumerList)); 120 + }, [sortOrder, sortField]); 121 + 122 + useEffect(() => { 123 + refreshPDSList(); 124 + // Refresh stats every 10 seconds 125 + const interval = setInterval(() => { 126 + refreshPDSList(); 127 + }, 10 * 1000); 128 + 129 + return () => clearInterval(interval); 130 + }, [sortField, sortOrder]); 131 + 132 + return ( 133 + <div className="mx-auto max-w-full"> 134 + {shouldShowNotification ? ( 135 + <Notification 136 + message={notification.message} 137 + alertType={notification.alertType} 138 + subMessage={notification.subMessage} 139 + autodismiss={notification.autodismiss} 140 + unshow={() => { 141 + setShouldShowNotification(false); 142 + setNotification({ message: "", alertType: "" }); 143 + }} 144 + show={shouldShowNotification} 145 + ></Notification> 146 + ) : ( 147 + <></> 148 + )} 149 + <div className="sm:flex sm:items-center"> 150 + <div className="sm:flex-auto"> 151 + <h1 className="text-2xl font-semibold leading-6 text-gray-900"> 152 + Consumer Connections 153 + </h1> 154 + <p className="mt-2 text-sm text-gray-700"> 155 + A list of all websocket consumers actively connected to the Relay 156 + </p> 157 + </div> 158 + </div> 159 + <div className="mt-8 flow-root"> 160 + <div className="shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg sm:rounded-b-none overflow-x-auto"> 161 + <table className="min-w-full divide-y divide-gray-300"> 162 + <thead className="bg-gray-50"> 163 + <tr> 164 + <th 165 + scope="col" 166 + className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6" 167 + > 168 + <a 169 + href="#" 170 + className="group inline-flex" 171 + onClick={() => { 172 + setSortField("ID"); 173 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 174 + }} 175 + > 176 + ID 177 + <span 178 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "ID" 179 + ? "group-hover:bg-gray-200" 180 + : "invisible group-hover:visible group-focus:visible" 181 + }`} 182 + > 183 + {sortField === "ID" && sortOrder === "asc" ? ( 184 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 185 + ) : ( 186 + <ChevronDownIcon 187 + className="h-5 w-5" 188 + aria-hidden="true" 189 + /> 190 + )} 191 + </span> 192 + </a> 193 + </th> 194 + <th 195 + scope="col" 196 + className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" 197 + > 198 + <a 199 + href="#" 200 + className="group inline-flex" 201 + onClick={() => { 202 + setSortField("RemoteAddr"); 203 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 204 + }} 205 + > 206 + Remote Address 207 + <span 208 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "RemoteAddr" 209 + ? "group-hover:bg-gray-200" 210 + : "invisible group-hover:visible group-focus:visible" 211 + }`} 212 + > 213 + {sortField === "RemoteAddr" && sortOrder === "asc" ? ( 214 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 215 + ) : ( 216 + <ChevronDownIcon 217 + className="h-5 w-5" 218 + aria-hidden="true" 219 + /> 220 + )} 221 + </span> 222 + </a> 223 + </th> 224 + <th 225 + scope="col" 226 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 227 + > 228 + <a 229 + href="#" 230 + className="group inline-flex" 231 + onClick={() => { 232 + setSortField("UserAgent"); 233 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 234 + }} 235 + > 236 + User Agent 237 + <span 238 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "UserAgent" 239 + ? "group-hover:bg-gray-200" 240 + : "invisible group-hover:visible group-focus:visible" 241 + }`} 242 + > 243 + {sortField === "UserAgent" && sortOrder === "asc" ? ( 244 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 245 + ) : ( 246 + <ChevronDownIcon 247 + className="h-5 w-5" 248 + aria-hidden="true" 249 + /> 250 + )} 251 + </span> 252 + </a> 253 + </th> 254 + <th 255 + scope="col" 256 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 257 + > 258 + <a 259 + href="#" 260 + className="group inline-flex" 261 + onClick={() => { 262 + setSortField("EventsConsumed"); 263 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 264 + }} 265 + > 266 + Events Consumed 267 + <span 268 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "EventsConsumed" 269 + ? "group-hover:bg-gray-200" 270 + : "invisible group-hover:visible group-focus:visible" 271 + }`} 272 + > 273 + {sortField === "EventsConsumed" && sortOrder === "asc" ? ( 274 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 275 + ) : ( 276 + <ChevronDownIcon 277 + className="h-5 w-5" 278 + aria-hidden="true" 279 + /> 280 + )} 281 + </span> 282 + </a> 283 + </th> 284 + <th 285 + scope="col" 286 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 287 + > 288 + <a 289 + href="#" 290 + className="group inline-flex" 291 + onClick={() => { 292 + setSortField("ConnectedAt"); 293 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 294 + }} 295 + > 296 + Connected At 297 + <span 298 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "ConnectedAt" 299 + ? "group-hover:bg-gray-200" 300 + : "invisible group-hover:visible group-focus:visible" 301 + }`} 302 + > 303 + {sortField === "ConnectedAt" && sortOrder === "asc" ? ( 304 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 305 + ) : ( 306 + <ChevronDownIcon 307 + className="h-5 w-5" 308 + aria-hidden="true" 309 + /> 310 + )} 311 + </span> 312 + </a> 313 + </th> 314 + </tr> 315 + </thead> 316 + <tbody className="divide-y divide-gray-200 bg-white"> 317 + {consumerList && 318 + consumerList.map((consumer) => { 319 + return ( 320 + <tr key={consumer.ID}> 321 + <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 text-left"> 322 + {consumer.ID} 323 + </td> 324 + <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-left"> 325 + {consumer.RemoteAddr} 326 + </td> 327 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 w-8 pr-6"> 328 + {consumer.UserAgent} 329 + </td> 330 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 w-8 pr-6"> 331 + {consumer.EventsConsumed?.toLocaleString()} 332 + </td> 333 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 334 + {consumer.ConnectedAt.toLocaleString()} 335 + </td> 336 + </tr> 337 + ); 338 + })} 339 + </tbody> 340 + </table> 341 + </div> 342 + </div> 343 + </div> 344 + ); 345 + }; 346 + 347 + export default Consumers;
+112
cmd/rerelay/relay-admin-ui/src/components/Dash/ConfirmModal.tsx
··· 1 + import { Fragment, useState } from "react"; 2 + import { Dialog, Transition } from "@headlessui/react"; 3 + import { XCircleIcon, XMarkIcon } from "@heroicons/react/24/outline"; 4 + 5 + import { PDS } from "../../models/pds"; 6 + 7 + interface ConfirmModalProps { 8 + action: { 9 + type: "block" | "disconnect"; 10 + pds: PDS; 11 + }; 12 + onConfirm: () => void; 13 + onCancel: () => void; 14 + } 15 + 16 + const ConfirmModal = ({ action, onConfirm, onCancel }: ConfirmModalProps) => { 17 + const [open, setOpen] = useState(true); 18 + 19 + const handleConfirm = () => { 20 + onConfirm(); 21 + setOpen(false); 22 + }; 23 + 24 + const handleCancel = () => { 25 + onCancel(); 26 + setOpen(false); 27 + }; 28 + 29 + return ( 30 + <Transition.Root show={open} as={Fragment}> 31 + <Dialog as="div" className="relative z-10" onClose={setOpen}> 32 + <Transition.Child 33 + as={Fragment} 34 + enter="ease-out duration-300" 35 + enterFrom="opacity-0" 36 + enterTo="opacity-100" 37 + leave="ease-in duration-200" 38 + leaveFrom="opacity-100" 39 + leaveTo="opacity-0" 40 + > 41 + <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> 42 + </Transition.Child> 43 + 44 + <div className="fixed inset-0 z-10 overflow-y-auto"> 45 + <div className="flex min-h-full items-center justify-center p-4 text-center sm:items-center sm:p-0"> 46 + <Transition.Child 47 + as={Fragment} 48 + enter="ease-out duration-300" 49 + enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 50 + enterTo="opacity-100 translate-y-0 sm:scale-100" 51 + leave="ease-in duration-200" 52 + leaveFrom="opacity-100 translate-y-0 sm:scale-100" 53 + leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 54 + > 55 + <Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> 56 + <div> 57 + <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100"> 58 + {action.type === "block" ? ( 59 + <XCircleIcon 60 + className="h-6 w-6 text-yellow-600" 61 + aria-hidden="true" 62 + /> 63 + ) : ( 64 + <XMarkIcon 65 + className="h-6 w-6 text-yellow-600" 66 + aria-hidden="true" 67 + /> 68 + )} 69 + </div> 70 + <div className="mt-3 text-center sm:mt-5"> 71 + <Dialog.Title 72 + as="h3" 73 + className="text-lg font-medium leading-6 text-gray-900" 74 + > 75 + {action.type === "block" 76 + ? "Block Host" 77 + : "Disconnect Host"} 78 + </Dialog.Title> 79 + <div className="mt-2"> 80 + <p className="text-sm text-gray-500"> 81 + Are you sure you want to {action.type}{" "} 82 + {action.pds!.Host}? 83 + </p> 84 + </div> 85 + </div> 86 + </div> 87 + <div className="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense"> 88 + <button 89 + type="button" 90 + className="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:col-start-2" 91 + onClick={handleConfirm} 92 + > 93 + Confirm 94 + </button> 95 + <button 96 + type="button" 97 + className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:col-start-1 sm:mt-0" 98 + onClick={handleCancel} 99 + > 100 + Cancel 101 + </button> 102 + </div> 103 + </Dialog.Panel> 104 + </Transition.Child> 105 + </div> 106 + </div> 107 + </Dialog> 108 + </Transition.Root> 109 + ); 110 + }; 111 + 112 + export default ConfirmModal;
+1341
cmd/rerelay/relay-admin-ui/src/components/Dash/Dash.tsx
··· 1 + import { 2 + ChevronDoubleLeftIcon, 3 + ChevronLeftIcon, 4 + ChevronRightIcon, 5 + MagnifyingGlassIcon, 6 + ShieldCheckIcon, 7 + ShieldExclamationIcon, 8 + } from "@heroicons/react/24/outline"; 9 + import { 10 + ArrowPathIcon, 11 + CheckCircleIcon, 12 + CheckIcon, 13 + ChevronDownIcon, 14 + ChevronUpIcon, 15 + PencilSquareIcon, 16 + XCircleIcon, 17 + XMarkIcon, 18 + } from "@heroicons/react/24/solid"; 19 + import { FC, useEffect, useState } from "react"; 20 + import Notification, { 21 + NotificationMeta, 22 + NotificationType, 23 + } from "../Notification/Notification"; 24 + 25 + import { RELAY_HOST } from "../../constants"; 26 + import { PDS, PDSKey } from "../../models/pds"; 27 + 28 + import { Switch } from "@headlessui/react"; 29 + import { useNavigate } from "react-router-dom"; 30 + import ConfirmModal from "./ConfirmModal"; 31 + 32 + function classNames(...classes: string[]) { 33 + return classes.filter(Boolean).join(" "); 34 + } 35 + 36 + const Dash: FC<{}> = () => { 37 + const [pdsList, setPDSList] = useState<PDS[] | null>(null); 38 + const [fullPDSList, setFullPDSList] = useState<PDS[] | null>(null); 39 + const [sortField, setSortField] = useState<PDSKey>("ID"); 40 + const [sortOrder, setSortOrder] = useState<string>("asc"); 41 + const [pageNum, setPageNum] = useState<number>(1); 42 + const pageSize = 30; 43 + 44 + // Slurp Toggle Management 45 + const [slurpsEnabled, setSlurpsEnabled] = useState<boolean>(true); 46 + const [canToggleSlurps, setCanToggleSlurps] = useState<boolean>(true); 47 + const [newPDSLimit, setNewPDSLimit] = useState<number>(0); 48 + const [canSetNewPDSLimit, setCanSetNewPDSLimit] = useState<boolean>(true); 49 + 50 + // Notification Management 51 + const [shouldShowNotification, setShouldShowNotification] = 52 + useState<boolean>(false); 53 + const [notification, setNotification] = useState<NotificationMeta>({ 54 + message: "", 55 + alertType: "", 56 + }); 57 + 58 + // Modal state management 59 + const [modalAction, setModalAction] = useState<{ 60 + type: "block" | "disconnect"; 61 + pds: PDS; 62 + } | null>(null); 63 + const [modalConfirm, setModalConfirm] = useState<() => void>(() => { }); 64 + const [modalCancel, setModalCancel] = useState<() => void>(() => { }); 65 + 66 + const [editingPerSecondRateLimit, setEditingPerSecondRateLimimt] = 67 + useState<PDS | null>(null); 68 + const [editingPerHourRateLimit, setEditingPerHourRateLimit] = 69 + useState<PDS | null>(null); 70 + const [editingPerDayRateLimit, setEditingPerDayRateLimit] = 71 + useState<PDS | null>(null); 72 + const [editingRepoLimit, setEditingRepoLimit] = 73 + useState<PDS | null>(null); 74 + 75 + const [searchTerm, setSearchTerm] = useState<string | undefined>(undefined); 76 + 77 + const filterPDSList = (list: PDS[]): PDS[] => { 78 + // Filter the hostnames based on the search term 79 + if (searchTerm) { 80 + // Support RegEx search 81 + try { 82 + const regex = new RegExp(searchTerm, "i"); 83 + list = list.filter((pds) => { 84 + return regex.test(pds.Host); 85 + }); 86 + } catch (e) { 87 + // If the regex is invalid, just do a normal search 88 + list = list.filter((pds) => { 89 + return pds.Host.toLowerCase().includes(searchTerm.toLowerCase()); 90 + }); 91 + } 92 + } 93 + 94 + return list; 95 + }; 96 + 97 + const [adminToken, setAdminToken] = useState<string>( 98 + localStorage.getItem("admin_route_token") || "" 99 + ); 100 + const navigate = useNavigate(); 101 + 102 + const setAlertWithTimeout = ( 103 + type: NotificationType, 104 + message: string, 105 + dismiss: boolean 106 + ) => { 107 + setNotification({ 108 + message, 109 + alertType: type, 110 + autodismiss: dismiss, 111 + }); 112 + setShouldShowNotification(true); 113 + }; 114 + 115 + useEffect(() => { 116 + const token = localStorage.getItem("admin_route_token"); 117 + if (token) { 118 + setAdminToken(token); 119 + } else { 120 + navigate("/login"); 121 + } 122 + }, []); 123 + 124 + useEffect(() => { 125 + document.title = "Relay Admin Dashboard"; 126 + }, []); 127 + 128 + const refreshPDSList = () => { 129 + fetch(`${RELAY_HOST}/admin/pds/list`, { 130 + method: "GET", 131 + headers: { 132 + "Content-Type": "application/json", 133 + Authorization: `Bearer ${adminToken}`, 134 + }, 135 + }) 136 + .then((res) => res.json()) 137 + .then((res: PDS[]) => { 138 + if ("error" in res) { 139 + setAlertWithTimeout( 140 + "failure", 141 + `Failed to fetch PDS list: ${res.error}`, 142 + true 143 + ); 144 + return; 145 + } 146 + setFullPDSList(res); 147 + }) 148 + .catch((err) => { 149 + setAlertWithTimeout( 150 + "failure", 151 + `Failed to fetch PDS list: ${err}`, 152 + true 153 + ); 154 + }); 155 + }; 156 + 157 + const getSlurpsEnabled = () => { 158 + fetch(`${RELAY_HOST}/admin/subs/getEnabled`, { 159 + method: "GET", 160 + headers: { 161 + "Content-Type": "application/json", 162 + Authorization: `Bearer ${adminToken}`, 163 + }, 164 + }) 165 + .then((res) => res.json()) 166 + .then((res) => { 167 + if ("error" in res) { 168 + setAlertWithTimeout( 169 + "failure", 170 + `Failed to fetch slurp status: ${res.error}`, 171 + true 172 + ); 173 + return; 174 + } 175 + setSlurpsEnabled(res.enabled); 176 + }) 177 + .catch((err) => { 178 + setAlertWithTimeout( 179 + "failure", 180 + `Failed to fetch slurp status: ${err}`, 181 + true 182 + ); 183 + }); 184 + }; 185 + 186 + const requestSlurpsEnabledStateChange = (state: boolean) => { 187 + setCanToggleSlurps(false); 188 + fetch(`${RELAY_HOST}/admin/subs/setEnabled?enabled=${state}`, { 189 + method: "POST", 190 + headers: { 191 + "Content-Type": "application/json", 192 + Authorization: `Bearer ${adminToken}`, 193 + }, 194 + }) 195 + .then((res) => { 196 + setCanToggleSlurps(true); 197 + if (res.status !== 200) { 198 + setAlertWithTimeout( 199 + "failure", 200 + `Failed to set slurp status: ${res.status}`, 201 + true 202 + ); 203 + return; 204 + } 205 + setAlertWithTimeout( 206 + "success", 207 + `Successfully ${state ? "enabled" : "disabled"} new slurps`, 208 + true 209 + ); 210 + setSlurpsEnabled(state); 211 + }) 212 + .catch((err) => { 213 + setCanToggleSlurps(true); 214 + setAlertWithTimeout( 215 + "failure", 216 + `Failed to set slurp status: ${err}`, 217 + true 218 + ); 219 + }); 220 + }; 221 + 222 + const getNewPDSRateLimit = () => { 223 + fetch(`${RELAY_HOST}/admin/subs/perDayLimit`, { 224 + method: "GET", 225 + headers: { 226 + "Content-Type": "application/json", 227 + Authorization: `Bearer ${adminToken}`, 228 + }, 229 + }) 230 + .then((res) => res.json()) 231 + .then((res) => { 232 + if ("error" in res) { 233 + setAlertWithTimeout( 234 + "failure", 235 + `Failed to fetch New PDS rate limit: ${res.error}`, 236 + true 237 + ); 238 + return; 239 + } 240 + setNewPDSLimit(res.limit); 241 + }) 242 + .catch((err) => { 243 + setAlertWithTimeout( 244 + "failure", 245 + `Failed to fetch New PDS rate limit: ${err}`, 246 + true 247 + ); 248 + }); 249 + } 250 + 251 + const setNewPDSRateLimit = (limit: number) => { 252 + setCanSetNewPDSLimit(false); 253 + fetch(`${RELAY_HOST}/admin/subs/setPerDayLimit?limit=${limit}`, { 254 + method: "POST", 255 + headers: { 256 + "Content-Type": "application/json", 257 + Authorization: `Bearer ${adminToken}`, 258 + }, 259 + 260 + }) 261 + .then((res) => { 262 + setCanSetNewPDSLimit(true); 263 + if (res.status !== 200) { 264 + setAlertWithTimeout( 265 + "failure", 266 + `Failed to set New PDS rate limit: ${res.status}`, 267 + true 268 + ); 269 + return; 270 + } 271 + setAlertWithTimeout( 272 + "success", 273 + `Successfully set New PDS rate limit to ${limit} / day`, 274 + true 275 + ); 276 + setNewPDSLimit(limit); 277 + }) 278 + .catch((err) => { 279 + setCanSetNewPDSLimit(true); 280 + setAlertWithTimeout( 281 + "failure", 282 + `Failed to set New PDS rate limit: ${err}`, 283 + true 284 + ); 285 + }); 286 + } 287 + 288 + const requestCrawlHost = (host: string) => { 289 + fetch(`${RELAY_HOST}/xrpc/com.atproto.sync.requestCrawl`, { 290 + method: "POST", 291 + headers: { 292 + "Content-Type": "application/json", 293 + }, 294 + body: JSON.stringify({ 295 + hostname: host, 296 + }), 297 + }).then((res) => { 298 + if (res.status !== 200) { 299 + setAlertWithTimeout( 300 + "failure", 301 + `Failed to request crawl: ${res.statusText} (${res.status})`, 302 + true 303 + ); 304 + } else { 305 + setAlertWithTimeout("success", "Successfully requested crawl", true); 306 + } 307 + refreshPDSList(); 308 + }); 309 + }; 310 + 311 + const requestDisconnectHost = (host: string, shouldBlock: boolean) => { 312 + fetch( 313 + `${RELAY_HOST}/admin/subs/killUpstream?host=${host}&block=${shouldBlock}`, 314 + { 315 + method: "POST", 316 + headers: { 317 + "Content-Type": "application/json", 318 + Authorization: `Bearer ${adminToken}`, 319 + }, 320 + } 321 + ).then((res) => { 322 + if (res.status !== 200) { 323 + setAlertWithTimeout( 324 + "failure", 325 + `Failed to request ${shouldBlock ? "block" : "disconnect"}: ${res.statusText 326 + } (${res.status})`, 327 + true 328 + ); 329 + } else { 330 + setAlertWithTimeout( 331 + "success", 332 + `Successfully requested ${shouldBlock ? "block" : "disconnect"}`, 333 + true 334 + ); 335 + } 336 + refreshPDSList(); 337 + }); 338 + }; 339 + 340 + const requestBlockHost = (host: string) => { 341 + fetch(`${RELAY_HOST}/admin/pds/block?host=${host}`, { 342 + method: "POST", 343 + headers: { 344 + "Content-Type": "application/json", 345 + Authorization: `Bearer ${adminToken}`, 346 + }, 347 + }).then((res) => { 348 + if (res.status !== 200) { 349 + setAlertWithTimeout( 350 + "failure", 351 + `Failed to request block: ${res.statusText} (${res.status})`, 352 + true 353 + ); 354 + } else { 355 + setAlertWithTimeout("success", "Successfully requested block", true); 356 + } 357 + refreshPDSList(); 358 + }); 359 + }; 360 + const requestUnblockHost = (host: string) => { 361 + fetch(`${RELAY_HOST}/admin/pds/unblock?host=${host}`, { 362 + method: "POST", 363 + headers: { 364 + "Content-Type": "application/json", 365 + Authorization: `Bearer ${adminToken}`, 366 + }, 367 + }).then((res) => { 368 + if (res.status !== 200) { 369 + setAlertWithTimeout( 370 + "failure", 371 + `Failed to request unblock: ${res.statusText} (${res.status})`, 372 + true 373 + ); 374 + } else { 375 + setAlertWithTimeout("success", "Successfully requested unblock", true); 376 + } 377 + refreshPDSList(); 378 + }); 379 + }; 380 + 381 + const updateRateLimits = (pds: PDS) => { 382 + fetch( 383 + `${RELAY_HOST}/admin/pds/changeLimits`, 384 + { 385 + method: "POST", 386 + headers: { 387 + "Content-Type": "application/json", 388 + Authorization: `Bearer ${adminToken}`, 389 + }, 390 + body: JSON.stringify({ 391 + host: pds.Host, 392 + per_second: pds.PerSecondEventRate.Max, 393 + per_hour: pds.PerHourEventRate.Max, 394 + per_day: pds.PerDayEventRate.Max, 395 + repo_limit: pds.RepoLimit, 396 + }), 397 + } 398 + ).then((res) => { 399 + if (res.status !== 200) { 400 + setAlertWithTimeout( 401 + "failure", 402 + `Failed to change rate limit: ${res.statusText} (${res.status})`, 403 + true 404 + ); 405 + } else { 406 + setAlertWithTimeout("success", "Successfully changed rate limits", true); 407 + } 408 + refreshPDSList(); 409 + }); 410 + }; 411 + 412 + const handleBlockClick = (pds: PDS, shouldBlock: boolean) => { 413 + setModalAction({ 414 + type: shouldBlock ? "block" : "disconnect", 415 + pds, 416 + }); 417 + 418 + setModalConfirm(() => { 419 + return () => { 420 + console.log(shouldBlock ? "Blocking" : "Disconnecting"); 421 + if (shouldBlock && pds.HasActiveConnection) { 422 + requestDisconnectHost(pds.Host, true); 423 + } else if (pds.HasActiveConnection) { 424 + requestDisconnectHost(pds.Host, false); 425 + } else { 426 + requestBlockHost(pds.Host); 427 + } 428 + setModalAction(null); 429 + }; 430 + }); 431 + 432 + setModalCancel(() => { 433 + return () => { 434 + setModalAction(null); 435 + }; 436 + }); 437 + }; 438 + 439 + const sortPDSList = (list: PDS[]): PDS[] => { 440 + const sortedPDSs: PDS[] = [...list].sort((a, b) => { 441 + if (sortOrder === "asc") { 442 + if (a[sortField]! < b[sortField]!) { 443 + return -1; 444 + } 445 + if (a[sortField]! > b[sortField]!) { 446 + return 1; 447 + } 448 + } else { 449 + if (a[sortField]! < b[sortField]!) { 450 + return 1; 451 + } 452 + if (a[sortField]! > b[sortField]!) { 453 + return -1; 454 + } 455 + } 456 + return 0; 457 + }); 458 + return sortedPDSs; 459 + }; 460 + 461 + useEffect(() => { 462 + if (!fullPDSList) { 463 + return; 464 + } 465 + setPDSList(sortPDSList(filterPDSList(fullPDSList!))); 466 + }, [sortOrder, sortField, searchTerm, fullPDSList]); 467 + 468 + useEffect(() => { 469 + refreshPDSList(); 470 + getSlurpsEnabled(); 471 + getNewPDSRateLimit(); 472 + // Refresh stats every 60 seconds 473 + const interval = setInterval(() => { 474 + refreshPDSList(); 475 + getSlurpsEnabled(); 476 + getNewPDSRateLimit(); 477 + }, 60 * 1000); 478 + 479 + return () => clearInterval(interval); 480 + }, [sortField, sortOrder]); 481 + 482 + return ( 483 + <div className="mx-auto max-w-full"> 484 + {shouldShowNotification ? ( 485 + <Notification 486 + message={notification.message} 487 + alertType={notification.alertType} 488 + subMessage={notification.subMessage} 489 + autodismiss={notification.autodismiss} 490 + unshow={() => { 491 + setShouldShowNotification(false); 492 + setNotification({ message: "", alertType: "" }); 493 + }} 494 + show={shouldShowNotification} 495 + ></Notification> 496 + ) : ( 497 + <></> 498 + )} 499 + <div></div> 500 + <div className="sm:flex sm:items-center"> 501 + <div className="sm:flex-auto"> 502 + <h1 className="text-2xl font-semibold leading-6 text-gray-900"> 503 + PDS Connections 504 + </h1> 505 + <p className="mt-2 text-sm text-gray-700"> 506 + A list of all PDS connections and their current status. 507 + </p> 508 + </div> 509 + <div className="flex flex-col mt-5"> 510 + <div className="inline-flex mt-5 sm:mt-0 flex-col"> 511 + <Switch.Group as="div" className="flex items-center justify-between"> 512 + <span className="flex flex-grow flex-col mr-5"> 513 + <Switch.Label as="span" className="text-gray-900" passive> 514 + {slurpsEnabled ? ( 515 + <ShieldCheckIcon 516 + className="h-5 w-5 mr-2 mb-1 inline-block" 517 + aria-hidden="true" 518 + /> 519 + ) : ( 520 + <ShieldExclamationIcon 521 + className="h-5 w-5 mr-2 mb-1 inline-block" 522 + aria-hidden="true" 523 + /> 524 + )} 525 + <span className="text-md font-medium leading-6"> 526 + New Connections {slurpsEnabled ? "Enabled" : "Disabled"} 527 + </span> 528 + </Switch.Label> 529 + </span> 530 + <Switch 531 + checked={slurpsEnabled} 532 + onChange={requestSlurpsEnabledStateChange} 533 + disabled={!canToggleSlurps} 534 + className={classNames( 535 + slurpsEnabled ? "bg-green-600" : "bg-red-400", 536 + canToggleSlurps ? "cursor-pointer" : "cursor-not-allowed", 537 + "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2" 538 + )} 539 + > 540 + <span 541 + aria-hidden="true" 542 + className={classNames( 543 + slurpsEnabled ? "translate-x-5" : "translate-x-0", 544 + "pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" 545 + )} 546 + /> 547 + </Switch> 548 + </Switch.Group> 549 + </div> 550 + <div className="ml-4"> 551 + <div className="mt-2 flex rounded-md shadow-sm"> 552 + <div className="relative flex flex-grow items-stretch focus-within:z-10"> 553 + <input 554 + type="number" 555 + id="new-pds-rate-limit" 556 + name="new-pds-rate-limit" 557 + // Hides the up/down arrows on number inputs 558 + className="[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none block w-full rounded-none rounded-l-md border-0 py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" 559 + value={newPDSLimit} 560 + aria-describedby="rate-limit" 561 + onChange={(e) => { 562 + setNewPDSLimit(parseInt(e.target.value)); 563 + }} 564 + /> 565 + <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> 566 + <span className="text-gray-500 sm:text-sm" id="price-currency"> 567 + PDS / Day 568 + </span> 569 + </div> 570 + </div> 571 + <button 572 + type="button" 573 + className="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50" 574 + disabled={!canSetNewPDSLimit} 575 + onClick={() => { 576 + setNewPDSRateLimit(newPDSLimit); 577 + }} 578 + > 579 + Update 580 + </button> 581 + </div> 582 + </div> 583 + </div> 584 + 585 + </div> 586 + <div className="flex flex-1 items-center justify-center py-2 lg:justify-start"> 587 + <div className="w-full max-w-lg lg:max-w-xs"> 588 + <label htmlFor="search" className="sr-only"> 589 + Search 590 + </label> 591 + <div className="relative"> 592 + <div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"> 593 + <MagnifyingGlassIcon className="h-5 w-5 text-gray-400" aria-hidden="true" /> 594 + </div> 595 + <input 596 + id="search" 597 + name="search" 598 + className="block w-full rounded-md border-0 bg-white py-1.5 pl-10 pr-3 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" 599 + placeholder="Search" 600 + type="search" 601 + onChange={(e) => { 602 + setSearchTerm(e.target.value); 603 + setPageNum(1); 604 + }} 605 + value={searchTerm || ""} 606 + /> 607 + </div> 608 + </div> 609 + </div> 610 + 611 + <div className="mt-8 flow-root"> 612 + <div className="shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg sm:rounded-b-none overflow-x-auto"> 613 + <table className="min-w-full divide-y divide-gray-300"> 614 + <thead className="bg-gray-50"> 615 + <tr> 616 + <th 617 + scope="col" 618 + className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6" 619 + > 620 + <a 621 + href="#" 622 + className="group inline-flex" 623 + onClick={() => { 624 + setSortField("ID"); 625 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 626 + }} 627 + > 628 + ID 629 + <span 630 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "ID" 631 + ? "group-hover:bg-gray-200" 632 + : "invisible group-hover:visible group-focus:visible" 633 + }`} 634 + > 635 + {sortField === "ID" && sortOrder === "asc" ? ( 636 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 637 + ) : ( 638 + <ChevronDownIcon 639 + className="h-5 w-5" 640 + aria-hidden="true" 641 + /> 642 + )} 643 + </span> 644 + </a> 645 + </th> 646 + <th 647 + scope="col" 648 + className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900" 649 + > 650 + <a 651 + href="#" 652 + className="group inline-flex" 653 + onClick={() => { 654 + setSortField("Host"); 655 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 656 + }} 657 + > 658 + Host 659 + <span 660 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "Host" 661 + ? "group-hover:bg-gray-200" 662 + : "invisible group-hover:visible group-focus:visible" 663 + }`} 664 + > 665 + {sortField === "Host" && sortOrder === "asc" ? ( 666 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 667 + ) : ( 668 + <ChevronDownIcon 669 + className="h-5 w-5" 670 + aria-hidden="true" 671 + /> 672 + )} 673 + </span> 674 + </a> 675 + </th> 676 + <th 677 + scope="col" 678 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 679 + > 680 + <a 681 + href="#" 682 + className="group inline-flex" 683 + onClick={() => { 684 + setSortField("HasActiveConnection"); 685 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 686 + }} 687 + > 688 + Connected 689 + <span 690 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "HasActiveConnection" 691 + ? "group-hover:bg-gray-200" 692 + : "invisible group-hover:visible group-focus:visible" 693 + }`} 694 + > 695 + {sortField === "HasActiveConnection" && 696 + sortOrder === "asc" ? ( 697 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 698 + ) : ( 699 + <ChevronDownIcon 700 + className="h-5 w-5" 701 + aria-hidden="true" 702 + /> 703 + )} 704 + </span> 705 + </a> 706 + </th> 707 + <th 708 + scope="col" 709 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 710 + > 711 + <a 712 + href="#" 713 + className="group inline-flex" 714 + onClick={() => { 715 + setSortField("Blocked"); 716 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 717 + }} 718 + > 719 + Permitted 720 + <span 721 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "Blocked" 722 + ? "group-hover:bg-gray-200" 723 + : "invisible group-hover:visible group-focus:visible" 724 + }`} 725 + > 726 + {sortField === "Blocked" && sortOrder === "asc" ? ( 727 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 728 + ) : ( 729 + <ChevronDownIcon 730 + className="h-5 w-5" 731 + aria-hidden="true" 732 + /> 733 + )} 734 + </span> 735 + </a> 736 + </th> 737 + <th 738 + scope="col" 739 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 740 + > 741 + <a 742 + href="#" 743 + className="group inline-flex" 744 + onClick={() => { 745 + setSortField("RepoCount"); 746 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 747 + }} 748 + > 749 + Users 750 + <span 751 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "RepoCount" 752 + ? "group-hover:bg-gray-200" 753 + : "invisible group-hover:visible group-focus:visible" 754 + }`} 755 + > 756 + {sortField === "RepoCount" && sortOrder === "asc" ? ( 757 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 758 + ) : ( 759 + <ChevronDownIcon 760 + className="h-5 w-5" 761 + aria-hidden="true" 762 + /> 763 + )} 764 + </span> 765 + </a> 766 + </th> 767 + <th 768 + scope="col" 769 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 770 + > 771 + <a 772 + href="#" 773 + className="group inline-flex" 774 + onClick={() => { 775 + setSortField("EventsSeenSinceStartup"); 776 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 777 + }} 778 + > 779 + Events Seen 780 + <span 781 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "EventsSeenSinceStartup" 782 + ? "group-hover:bg-gray-200" 783 + : "invisible group-hover:visible group-focus:visible" 784 + }`} 785 + > 786 + {sortField === "EventsSeenSinceStartup" && 787 + sortOrder === "asc" ? ( 788 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 789 + ) : ( 790 + <ChevronDownIcon 791 + className="h-5 w-5" 792 + aria-hidden="true" 793 + /> 794 + )} 795 + </span> 796 + </a> 797 + </th> 798 + <th 799 + scope="col" 800 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 801 + > 802 + <a 803 + href="#" 804 + className="group inline-flex" 805 + onClick={() => { 806 + setSortField("Cursor"); 807 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 808 + }} 809 + > 810 + Cursor 811 + <span 812 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "Cursor" 813 + ? "group-hover:bg-gray-200" 814 + : "invisible group-hover:visible group-focus:visible" 815 + }`} 816 + > 817 + {sortField === "Cursor" && sortOrder === "asc" ? ( 818 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 819 + ) : ( 820 + <ChevronDownIcon 821 + className="h-5 w-5" 822 + aria-hidden="true" 823 + /> 824 + )} 825 + </span> 826 + </a> 827 + </th> 828 + <th 829 + scope="col" 830 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 831 + > 832 + <a href="#" className="group inline-flex"> 833 + Events Per Second Limit 834 + </a> 835 + </th> 836 + <th 837 + scope="col" 838 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 839 + > 840 + <a href="#" className="group inline-flex"> 841 + Per Hour Limit 842 + </a> 843 + </th> 844 + <th 845 + scope="col" 846 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 847 + > 848 + <a href="#" className="group inline-flex"> 849 + Per Day Limit 850 + </a> 851 + </th> 852 + <th 853 + scope="col" 854 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 855 + > 856 + <a href="#" className="group inline-flex"> 857 + Repo Limit 858 + </a> 859 + </th> 860 + <th 861 + scope="col" 862 + className="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 pr-6 whitespace-nowrap" 863 + > 864 + <a 865 + href="#" 866 + className="group inline-flex" 867 + onClick={() => { 868 + setSortField("CreatedAt"); 869 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 870 + }} 871 + > 872 + First Seen 873 + <span 874 + className={`ml-2 flex-none rounded text-gray-400 ${sortField === "CreatedAt" 875 + ? "group-hover:bg-gray-200" 876 + : "invisible group-hover:visible group-focus:visible" 877 + }`} 878 + > 879 + {sortField === "CreatedAt" && sortOrder === "asc" ? ( 880 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 881 + ) : ( 882 + <ChevronDownIcon 883 + className="h-5 w-5" 884 + aria-hidden="true" 885 + /> 886 + )} 887 + </span> 888 + </a> 889 + </th> 890 + </tr> 891 + </thead> 892 + <tbody className="divide-y divide-gray-200 bg-white"> 893 + {pdsList && 894 + pdsList.map((pds, idx) => { 895 + if (idx < (pageNum - 1) * pageSize || idx >= pageNum * pageSize) { 896 + return null; 897 + } 898 + return ( 899 + <tr key={pds.ID}> 900 + <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 text-left"> 901 + {pds.ID} 902 + </td> 903 + <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 text-left"> 904 + {pds.Host} 905 + </td> 906 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 w-8 pr-6"> 907 + {pds.HasActiveConnection ? ( 908 + <div className="inline-flex justify-center w-full"> 909 + <CheckCircleIcon 910 + className="h-5 w-5 text-green-500 my-auto mr-2" 911 + aria-hidden="true" 912 + /> 913 + <button 914 + className="rounded-md p-1.5 hover:text-red-600 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-offset-2 focus:ring-offset-red-50" 915 + onClick={() => { 916 + handleBlockClick(pds, false); 917 + }} 918 + > 919 + <XMarkIcon 920 + className="h-5 w-5" 921 + aria-hidden="true" 922 + /> 923 + </button> 924 + </div> 925 + ) : ( 926 + <div className="inline-flex justify-center w-full"> 927 + <XCircleIcon 928 + className="h-5 w-5 text-red-500 mr-2 my-auto" 929 + aria-hidden="true" 930 + /> 931 + <button 932 + className="rounded-md p-1.5 hover:text-green-600 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" 933 + onClick={() => { 934 + requestCrawlHost(pds.Host); 935 + }} 936 + > 937 + <ArrowPathIcon 938 + className="h-5 w-5" 939 + aria-hidden="true" 940 + /> 941 + </button> 942 + </div> 943 + )} 944 + </td> 945 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 w-8 pr-6"> 946 + {pds.Blocked ? ( 947 + <div className="inline-flex justify-center w-full"> 948 + <XCircleIcon 949 + className="h-5 w-5 text-red-500 my-auto mr-2" 950 + aria-hidden="true" 951 + /> 952 + <button 953 + className="rounded-md p-1.5 hover:text-green-600 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" 954 + onClick={() => { 955 + requestUnblockHost(pds.Host); 956 + }} 957 + > 958 + <CheckIcon 959 + className="h-5 w-5" 960 + aria-hidden="true" 961 + /> 962 + </button> 963 + </div> 964 + ) : ( 965 + <div className="inline-flex justify-center w-full"> 966 + <CheckCircleIcon 967 + className="h-5 w-5 text-green-500 my-auto mr-2" 968 + aria-hidden="true" 969 + /> 970 + <button 971 + className="rounded-md p-1.5 hover:text-red-600 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-red-600 focus:ring-offset-2 focus:ring-offset-red-50" 972 + onClick={() => { 973 + handleBlockClick(pds, true); 974 + }} 975 + > 976 + <XMarkIcon 977 + className="h-5 w-5" 978 + aria-hidden="true" 979 + /> 980 + </button> 981 + </div> 982 + )} 983 + </td> 984 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 985 + {pds.RepoCount?.toLocaleString()} 986 + </td> 987 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 988 + {pds.EventsSeenSinceStartup?.toLocaleString()} 989 + </td> 990 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 991 + {pds.Cursor?.toLocaleString()} 992 + </td> 993 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 994 + <span 995 + className={ 996 + editingPerSecondRateLimit?.ID === pds.ID 997 + ? "hidden" 998 + : "" 999 + } 1000 + > 1001 + {pds.PerSecondEventRate.Max?.toLocaleString()} 1002 + /sec 1003 + </span> 1004 + <input 1005 + type="number" 1006 + name={`per-second-rate-limit-${pds.ID}`} 1007 + id={`per-second-rate-limit-${pds.ID}`} 1008 + className={ 1009 + `inline-block w-24 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6` + 1010 + (editingPerSecondRateLimit?.ID === pds.ID 1011 + ? "" 1012 + : " hidden") 1013 + } 1014 + defaultValue={pds.PerSecondEventRate.Max?.toLocaleString()} 1015 + /> 1016 + <a 1017 + href="#" 1018 + onClick={() => setEditingPerSecondRateLimimt(pds)} 1019 + className={editingPerSecondRateLimit ? "hidden" : ""} 1020 + > 1021 + <PencilSquareIcon 1022 + className="h-5 w-5 text-gray-500 ml-1 inline-block align-sub" 1023 + aria-hidden="true" 1024 + /> 1025 + </a> 1026 + <a 1027 + href="#" 1028 + onClick={() => { 1029 + const newRateLimit = document.getElementById( 1030 + `per-second-rate-limit-${pds.ID}` 1031 + ) as HTMLInputElement; 1032 + if (newRateLimit) { 1033 + pds.PerSecondEventRate.Max = +newRateLimit.value; 1034 + updateRateLimits(pds); 1035 + } 1036 + setEditingPerSecondRateLimimt(null); 1037 + }} 1038 + className={ 1039 + "rounded-md p-2 ml-1 hover:text-green-600 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" + 1040 + (editingPerSecondRateLimit?.ID === pds.ID 1041 + ? "" 1042 + : " hidden") 1043 + } 1044 + > 1045 + <CheckIcon 1046 + className="h-5 w-5 text-green-500 inline-block align-sub" 1047 + aria-hidden="true" 1048 + /> 1049 + </a> 1050 + </td> 1051 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 1052 + <span 1053 + className={ 1054 + editingPerHourRateLimit?.ID === pds.ID 1055 + ? "hidden" 1056 + : "" 1057 + } 1058 + > 1059 + {pds.PerHourEventRate.Max?.toLocaleString()} 1060 + /hour 1061 + </span> 1062 + <input 1063 + type="number" 1064 + name={`per-hour-rate-limit-${pds.ID}`} 1065 + id={`per-hour-rate-limit-${pds.ID}`} 1066 + className={ 1067 + `inline-block w-24 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6` + 1068 + (editingPerHourRateLimit?.ID === pds.ID 1069 + ? "" 1070 + : " hidden") 1071 + } 1072 + defaultValue={pds.PerHourEventRate.Max?.toLocaleString()} 1073 + /> 1074 + <a 1075 + href="#" 1076 + onClick={() => setEditingPerHourRateLimit(pds)} 1077 + className={editingPerHourRateLimit ? "hidden" : ""} 1078 + > 1079 + <PencilSquareIcon 1080 + className="h-5 w-5 text-gray-500 ml-1 inline-block align-sub" 1081 + aria-hidden="true" 1082 + /> 1083 + </a> 1084 + <a 1085 + href="#" 1086 + onClick={() => { 1087 + const newRateLimit = document.getElementById( 1088 + `per-hour-rate-limit-${pds.ID}` 1089 + ) as HTMLInputElement; 1090 + if (newRateLimit) { 1091 + pds.PerHourEventRate.Max = +newRateLimit.value; 1092 + updateRateLimits(pds); 1093 + } 1094 + setEditingPerHourRateLimit(null); 1095 + }} 1096 + className={ 1097 + "rounded-md p-2 ml-1 hover:text-green-600 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" + 1098 + (editingPerHourRateLimit?.ID === pds.ID 1099 + ? "" 1100 + : " hidden") 1101 + } 1102 + > 1103 + <CheckIcon 1104 + className="h-5 w-5 text-green-500 inline-block align-sub" 1105 + aria-hidden="true" 1106 + /> 1107 + </a> 1108 + </td> 1109 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 1110 + <span 1111 + className={ 1112 + editingPerDayRateLimit?.ID === pds.ID 1113 + ? "hidden" 1114 + : "" 1115 + } 1116 + > 1117 + {pds.PerDayEventRate.Max?.toLocaleString()} 1118 + /day 1119 + </span> 1120 + <input 1121 + type="number" 1122 + name={`per-day-limit-${pds.ID}`} 1123 + id={`per-day-rate-limit-${pds.ID}`} 1124 + className={ 1125 + `inline-block w-24 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6` + 1126 + (editingPerDayRateLimit?.ID === pds.ID 1127 + ? "" 1128 + : " hidden") 1129 + } 1130 + defaultValue={pds.PerDayEventRate.Max?.toLocaleString()} 1131 + /> 1132 + <a 1133 + href="#" 1134 + onClick={() => setEditingPerDayRateLimit(pds)} 1135 + className={editingPerDayRateLimit ? "hidden" : ""} 1136 + > 1137 + <PencilSquareIcon 1138 + className="h-5 w-5 text-gray-500 ml-1 inline-block align-sub" 1139 + aria-hidden="true" 1140 + /> 1141 + </a> 1142 + <a 1143 + href="#" 1144 + onClick={() => { 1145 + const newRateLimit = document.getElementById( 1146 + `per-day-rate-limit-${pds.ID}` 1147 + ) as HTMLInputElement; 1148 + if (newRateLimit) { 1149 + pds.PerDayEventRate.Max = +newRateLimit.value; 1150 + updateRateLimits(pds); 1151 + } 1152 + setEditingPerDayRateLimit(null); 1153 + }} 1154 + className={ 1155 + "rounded-md p-2 ml-1 hover:text-green-600 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" + 1156 + (editingPerDayRateLimit?.ID === pds.ID 1157 + ? "" 1158 + : " hidden") 1159 + } 1160 + > 1161 + <CheckIcon 1162 + className="h-5 w-5 text-green-500 inline-block align-sub" 1163 + aria-hidden="true" 1164 + /> 1165 + </a> 1166 + </td> 1167 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 1168 + <span 1169 + className={ 1170 + editingRepoLimit?.ID === pds.ID 1171 + ? "hidden" 1172 + : "" 1173 + } 1174 + > 1175 + {pds.RepoLimit?.toLocaleString()} 1176 + </span> 1177 + <input 1178 + type="number" 1179 + name={`repo-limit-${pds.ID}`} 1180 + id={`repo-limit-${pds.ID}`} 1181 + className={ 1182 + `inline-block w-24 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6` + 1183 + (editingRepoLimit?.ID === pds.ID 1184 + ? "" 1185 + : " hidden") 1186 + } 1187 + defaultValue={pds.RepoLimit?.toLocaleString()} 1188 + /> 1189 + <a 1190 + href="#" 1191 + onClick={() => setEditingRepoLimit(pds)} 1192 + className={editingRepoLimit ? "hidden" : ""} 1193 + > 1194 + <PencilSquareIcon 1195 + className="h-5 w-5 text-gray-500 ml-1 inline-block align-sub" 1196 + aria-hidden="true" 1197 + /> 1198 + </a> 1199 + <a 1200 + href="#" 1201 + onClick={() => { 1202 + const newLimit = document.getElementById( 1203 + `repo-limit-${pds.ID}` 1204 + ) as HTMLInputElement; 1205 + if (newLimit) { 1206 + pds.RepoLimit = +newLimit.value; 1207 + updateRateLimits(pds); 1208 + } 1209 + setEditingRepoLimit(null); 1210 + }} 1211 + className={ 1212 + "rounded-md p-2 ml-1 hover:text-green-600 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" + 1213 + (editingRepoLimit?.ID === pds.ID 1214 + ? "" 1215 + : " hidden") 1216 + } 1217 + > 1218 + <CheckIcon 1219 + className="h-5 w-5 text-green-500 inline-block align-sub" 1220 + aria-hidden="true" 1221 + /> 1222 + </a> 1223 + </td> 1224 + <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-400 text-center w-8 pr-6"> 1225 + {new Date(Date.parse(pds.CreatedAt)).toLocaleString()} 1226 + </td> 1227 + </tr> 1228 + ); 1229 + })} 1230 + </tbody> 1231 + </table> 1232 + </div> 1233 + {pdsList && pdsList.length > pageSize && ( 1234 + <div className="mt-4 flex-1 flex justify-between sm:justify-end"> 1235 + <div className="flex-1 flex justify-between sm:hidden"> 1236 + <button 1237 + onClick={() => { 1238 + if (pageNum > 1) { 1239 + setPageNum(pageNum - 1); 1240 + } 1241 + }} 1242 + disabled={pageNum <= 1} 1243 + className="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm cursor-pointer" 1244 + > 1245 + Previous 1246 + </button> 1247 + <button 1248 + onClick={() => { 1249 + if (pageNum < Math.ceil(pdsList.length / pageSize)) { 1250 + setPageNum(pageNum + 1); 1251 + } 1252 + }} 1253 + disabled={pageNum >= Math.ceil(pdsList.length / pageSize)} 1254 + className="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md shadow-sm cursor-pointer" 1255 + > 1256 + Next 1257 + </button> 1258 + </div> 1259 + <div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between"> 1260 + <div> 1261 + <p className="text-sm text-gray-700"> 1262 + Showing 1263 + <span className="font-medium"> {1 + (pageNum - 1) * pageSize} </span> 1264 + to 1265 + <span className="font-medium"> {Math.min(pageNum * pageSize, pdsList.length)} </span> 1266 + of 1267 + <span className="font-medium"> {pdsList.length} </span> 1268 + results 1269 + </p> 1270 + </div> 1271 + <div> 1272 + <nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination"> 1273 + <button 1274 + onClick={() => setPageNum(1)} 1275 + disabled={pageNum <= 1} 1276 + className="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-pointer" 1277 + > 1278 + <span className="sr-only">First</span> 1279 + <ChevronDoubleLeftIcon className="h-5 w-5" aria-hidden="true" /> 1280 + </button> 1281 + <button 1282 + onClick={() => { 1283 + if (pageNum > 1) { 1284 + setPageNum(pageNum - 1); 1285 + } 1286 + }} 1287 + disabled={pageNum <= 1} 1288 + className="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-pointer" 1289 + > 1290 + <span className="sr-only">Previous</span> 1291 + <ChevronLeftIcon className="h-5 w-5" aria-hidden="true" /> 1292 + </button> 1293 + {Array.from({ length: Math.ceil(pdsList.length / pageSize) }, (_, i) => i + 1).map((page) => ( 1294 + // Skip buttons more than 5 pages away from the current page 1295 + Math.abs(page - pageNum) > 5 ? null : ( 1296 + <button 1297 + key={page} 1298 + onClick={() => setPageNum(page)} 1299 + className={classNames( 1300 + page === pageNum 1301 + ? "z-10 bg-indigo-50 border-indigo-500 text-indigo-600" 1302 + : "bg-white border-gray-300 text-gray-500", 1303 + "relative inline-flex items-center px-4 py-2 text-sm font-medium border cursor-pointer" 1304 + )} 1305 + > 1306 + {page} 1307 + </button> 1308 + ) 1309 + ))} 1310 + <button 1311 + onClick={() => { 1312 + if (pageNum < Math.ceil(pdsList.length / pageSize)) { 1313 + setPageNum(pageNum + 1); 1314 + } 1315 + }} 1316 + disabled={pageNum >= Math.ceil(pdsList.length / pageSize)} 1317 + className="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-white border border-gray-300 cursor-pointer" 1318 + > 1319 + <span className="sr-only">Next</span> 1320 + <ChevronRightIcon className="h-5 w-5" aria-hidden="true" /> 1321 + </button> 1322 + </nav> 1323 + </div> 1324 + </div> 1325 + </div> 1326 + )} 1327 + </div> 1328 + { 1329 + modalAction && ( 1330 + <ConfirmModal 1331 + action={modalAction} 1332 + onConfirm={modalConfirm} 1333 + onCancel={modalCancel} 1334 + /> 1335 + ) 1336 + } 1337 + </div > 1338 + ); 1339 + }; 1340 + 1341 + export default Dash;
+107
cmd/rerelay/relay-admin-ui/src/components/Domains/ConfirmDomainBanModal.tsx
··· 1 + import { Fragment, useState } from "react"; 2 + import { Dialog, Transition } from "@headlessui/react"; 3 + import { XCircleIcon } from "@heroicons/react/24/outline"; 4 + 5 + interface ConfirmModalProps { 6 + action: { 7 + type: "ban" | "unban"; 8 + domain: string; 9 + }; 10 + onConfirm: () => void; 11 + onCancel: () => void; 12 + } 13 + 14 + const ConfirmDomainBanModal = ({ 15 + action, 16 + onConfirm, 17 + onCancel, 18 + }: ConfirmModalProps) => { 19 + const [open, setOpen] = useState(true); 20 + 21 + const handleConfirm = () => { 22 + onConfirm(); 23 + setOpen(false); 24 + }; 25 + 26 + const handleCancel = () => { 27 + onCancel(); 28 + setOpen(false); 29 + }; 30 + 31 + return ( 32 + <Transition.Root show={open} as={Fragment}> 33 + <Dialog as="div" className="relative z-10" onClose={setOpen}> 34 + <Transition.Child 35 + as={Fragment} 36 + enter="ease-out duration-300" 37 + enterFrom="opacity-0" 38 + enterTo="opacity-100" 39 + leave="ease-in duration-200" 40 + leaveFrom="opacity-100" 41 + leaveTo="opacity-0" 42 + > 43 + <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> 44 + </Transition.Child> 45 + 46 + <div className="fixed inset-0 z-10 overflow-y-auto"> 47 + <div className="flex min-h-full items-center justify-center p-4 text-center sm:items-center sm:p-0"> 48 + <Transition.Child 49 + as={Fragment} 50 + enter="ease-out duration-300" 51 + enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 52 + enterTo="opacity-100 translate-y-0 sm:scale-100" 53 + leave="ease-in duration-200" 54 + leaveFrom="opacity-100 translate-y-0 sm:scale-100" 55 + leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 56 + > 57 + <Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> 58 + <div> 59 + <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100"> 60 + <XCircleIcon 61 + className="h-6 w-6 text-yellow-600" 62 + aria-hidden="true" 63 + /> 64 + </div> 65 + <div className="mt-3 text-center sm:mt-5"> 66 + <Dialog.Title 67 + as="h3" 68 + className="text-lg font-medium leading-6 text-gray-900" 69 + > 70 + {`${action.type[0].toLocaleUpperCase()}${action.type.substring( 71 + 1 72 + )}`}{" "} 73 + Domain 74 + </Dialog.Title> 75 + <div className="mt-2"> 76 + <p className="text-sm text-gray-500"> 77 + Are you sure you want to {action.type} {action.domain}? 78 + </p> 79 + </div> 80 + </div> 81 + </div> 82 + <div className="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense"> 83 + <button 84 + type="button" 85 + className="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:col-start-2" 86 + onClick={handleConfirm} 87 + > 88 + Confirm 89 + </button> 90 + <button 91 + type="button" 92 + className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:col-start-1 sm:mt-0" 93 + onClick={handleCancel} 94 + > 95 + Cancel 96 + </button> 97 + </div> 98 + </Dialog.Panel> 99 + </Transition.Child> 100 + </div> 101 + </div> 102 + </Dialog> 103 + </Transition.Root> 104 + ); 105 + }; 106 + 107 + export default ConfirmDomainBanModal;
+344
cmd/rerelay/relay-admin-ui/src/components/Domains/Domains.tsx
··· 1 + import { 2 + CheckIcon, 3 + ChevronDownIcon, 4 + ChevronUpIcon, 5 + } from "@heroicons/react/24/solid"; 6 + import { FC, useEffect, useState } from "react"; 7 + import Notification, { 8 + NotificationMeta, 9 + NotificationType, 10 + } from "../Notification/Notification"; 11 + 12 + import { RELAY_HOST } from "../../constants"; 13 + 14 + import { useNavigate } from "react-router-dom"; 15 + import ConfirmDomainBanModal from "./ConfirmDomainBanModal"; 16 + import { ShieldExclamationIcon } from "@heroicons/react/24/outline"; 17 + 18 + interface DomainBans { 19 + banned_domains: string[]; 20 + } 21 + 22 + const Domains: FC<{}> = () => { 23 + const [bannedDomains, setBannedDomains] = useState<string[]>([]); 24 + const [sortOrder, setSortOrder] = useState<string>("asc"); 25 + const [domainToBan, setDomainToBan] = useState<string>(""); 26 + 27 + // Notification Management 28 + const [shouldShowNotification, setShouldShowNotification] = 29 + useState<boolean>(false); 30 + const [notification, setNotification] = useState<NotificationMeta>({ 31 + message: "", 32 + alertType: "", 33 + }); 34 + 35 + // Modal state management 36 + const [modalAction, setModalAction] = useState<{ 37 + domain: string; 38 + type: "ban" | "unban"; 39 + } | null>(null); 40 + const [modalConfirm, setModalConfirm] = useState<() => void>(() => { }); 41 + const [modalCancel, setModalCancel] = useState<() => void>(() => { }); 42 + 43 + const [adminToken, setAdminToken] = useState<string>( 44 + localStorage.getItem("admin_route_token") || "" 45 + ); 46 + const navigate = useNavigate(); 47 + 48 + const setAlertWithTimeout = ( 49 + type: NotificationType, 50 + message: string, 51 + dismiss: boolean 52 + ) => { 53 + setNotification({ 54 + message, 55 + alertType: type, 56 + autodismiss: dismiss, 57 + }); 58 + setShouldShowNotification(true); 59 + }; 60 + 61 + useEffect(() => { 62 + const token = localStorage.getItem("admin_route_token"); 63 + if (token) { 64 + setAdminToken(token); 65 + } else { 66 + navigate("/login"); 67 + } 68 + }, []); 69 + 70 + useEffect(() => { 71 + document.title = "Relay Admin Dashboard"; 72 + }, []); 73 + 74 + const refreshDomainBanList = () => { 75 + fetch(`${RELAY_HOST}/admin/subs/listDomainBans`, { 76 + method: "GET", 77 + headers: { 78 + "Content-Type": "application/json", 79 + Authorization: `Bearer ${adminToken}`, 80 + }, 81 + }) 82 + .then((res) => res.json()) 83 + .then((res: DomainBans) => { 84 + if ("error" in res) { 85 + setAlertWithTimeout( 86 + "failure", 87 + `Failed to fetch Domain Ban list: ${res.error}`, 88 + true 89 + ); 90 + return; 91 + } 92 + const sortedList = sortDomainBanList(res.banned_domains); 93 + setBannedDomains(sortedList); 94 + }) 95 + .catch((err) => { 96 + setAlertWithTimeout( 97 + "failure", 98 + `Failed to fetch PDS list: ${err}`, 99 + true 100 + ); 101 + }); 102 + }; 103 + 104 + const requestBanDomain = (domain: string) => { 105 + fetch(`${RELAY_HOST}/admin/subs/banDomain`, { 106 + method: "POST", 107 + headers: { 108 + "Content-Type": "application/json", 109 + Authorization: `Bearer ${adminToken}`, 110 + }, 111 + body: JSON.stringify({ 112 + Domain: domain, 113 + }), 114 + }) 115 + .then((res) => res.json()) 116 + .then((res) => { 117 + if (res.error) { 118 + setAlertWithTimeout( 119 + "failure", 120 + `Failed to ban domain: ${res.error}`, 121 + true 122 + ); 123 + } else { 124 + setAlertWithTimeout( 125 + "success", 126 + `Successfully banned domain ${domain}`, 127 + true 128 + ); 129 + } 130 + refreshDomainBanList(); 131 + }) 132 + .catch((err) => { 133 + setAlertWithTimeout("failure", `Failed to ban domain: ${err}`, true); 134 + }); 135 + }; 136 + 137 + const requestUnbanDomain = (domain: string) => { 138 + fetch(`${RELAY_HOST}/admin/subs/unbanDomain`, { 139 + method: "POST", 140 + headers: { 141 + "Content-Type": "application/json", 142 + Authorization: `Bearer ${adminToken}`, 143 + }, 144 + body: JSON.stringify({ 145 + Domain: domain, 146 + }), 147 + }).then((res) => { 148 + if (res.status !== 200) { 149 + setAlertWithTimeout( 150 + "failure", 151 + `Failed to unban domain: ${res.statusText} (${res.status})`, 152 + true 153 + ); 154 + } else { 155 + setAlertWithTimeout( 156 + "success", 157 + `Successfully unbanned domain ${domain}`, 158 + true 159 + ); 160 + } 161 + refreshDomainBanList(); 162 + }); 163 + }; 164 + 165 + const handleBanUnbaonDomain = (domain: string, type: "ban" | "unban") => { 166 + setModalAction({ domain, type }); 167 + 168 + setModalConfirm(() => { 169 + return () => { 170 + if (type === "ban") requestBanDomain(domain); 171 + else requestUnbanDomain(domain); 172 + 173 + setModalAction(null); 174 + }; 175 + }); 176 + 177 + setModalCancel(() => { 178 + return () => { 179 + setModalAction(null); 180 + }; 181 + }); 182 + }; 183 + 184 + const sortDomainBanList = (list: string[]): string[] => { 185 + const sortedDomains = [...list].sort(); 186 + return sortedDomains; 187 + }; 188 + 189 + useEffect(() => { 190 + if (!bannedDomains) { 191 + return; 192 + } 193 + setBannedDomains(sortDomainBanList(bannedDomains)); 194 + }, [sortOrder]); 195 + 196 + useEffect(() => { 197 + refreshDomainBanList(); 198 + // Refresh stats every 10 seconds 199 + const interval = setInterval(() => { 200 + refreshDomainBanList(); 201 + }, 10 * 1000); 202 + 203 + return () => clearInterval(interval); 204 + }, [sortOrder]); 205 + 206 + return ( 207 + <div className="mx-auto max-w-full"> 208 + {shouldShowNotification ? ( 209 + <Notification 210 + message={notification.message} 211 + alertType={notification.alertType} 212 + subMessage={notification.subMessage} 213 + autodismiss={notification.autodismiss} 214 + unshow={() => { 215 + setShouldShowNotification(false); 216 + setNotification({ message: "", alertType: "" }); 217 + }} 218 + show={shouldShowNotification} 219 + ></Notification> 220 + ) : ( 221 + <></> 222 + )} 223 + <div className="sm:flex sm:items-center"> 224 + <div className="sm:flex-auto"> 225 + <h1 className="text-2xl font-semibold leading-6 text-gray-900"> 226 + Banned Domains 227 + </h1> 228 + <p className="mt-2 text-sm text-gray-700"> 229 + A list of all currently banned domains. Any subdomains of these 230 + domains are also banned. 231 + </p> 232 + </div> 233 + <div className="flex-grow mt-5 sm:mt-0"> 234 + <div className="max-w-3xl w-full"> 235 + <label 236 + htmlFor="email" 237 + className="block text-sm font-medium leading-6 text-gray-900" 238 + > 239 + Domain 240 + </label> 241 + <div className="mt-2 inline-flex w-full"> 242 + <input 243 + type="text" 244 + name="domain" 245 + id="domain" 246 + className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" 247 + placeholder="noisy.pds.com" 248 + value={domainToBan} 249 + onChange={(e) => { 250 + setDomainToBan(e.target.value); 251 + }} 252 + /> 253 + <button 254 + type="button" 255 + onClick={() => { 256 + handleBanUnbaonDomain(domainToBan.trim(), "ban"); 257 + }} 258 + className="ml-2 inline-flex whitespace-nowrap items-center gap-x-1.5 rounded-md bg-red-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600" 259 + > 260 + <ShieldExclamationIcon 261 + className="-ml-0.5 h-5 w-5" 262 + aria-hidden="true" 263 + /> 264 + Ban Domain 265 + </button> 266 + </div> 267 + </div> 268 + </div> 269 + </div> 270 + 271 + <div className="mt-8 flow-root"> 272 + <div className="overflow-x-auto shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg sm:rounded-b-none"> 273 + <table className="min-w-full divide-y divide-gray-300"> 274 + <thead className="bg-gray-50"> 275 + <tr> 276 + <th 277 + scope="col" 278 + className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6" 279 + > 280 + <a 281 + href="#" 282 + className="group inline-flex" 283 + onClick={() => { 284 + setSortOrder(sortOrder === "asc" ? "desc" : "asc"); 285 + }} 286 + > 287 + Domain 288 + <span className="ml-2 flex-none rounded text-gray-400 group-hover:bg-gray-200"> 289 + {sortOrder === "asc" ? ( 290 + <ChevronUpIcon className="h-5 w-5" aria-hidden="true" /> 291 + ) : ( 292 + <ChevronDownIcon 293 + className="h-5 w-5" 294 + aria-hidden="true" 295 + /> 296 + )} 297 + </span> 298 + </a> 299 + </th> 300 + <th 301 + scope="col" 302 + className="py-3.5 pr-4 pl-3 text-right text-sm font-semibold text-gray-900 sm:pr-6" 303 + > 304 + Unban 305 + </th> 306 + </tr> 307 + </thead> 308 + <tbody className="divide-y divide-gray-200 bg-white"> 309 + {bannedDomains && 310 + bannedDomains.map((domain) => { 311 + return ( 312 + <tr key={domain}> 313 + <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900 sm:pl-6 text-left"> 314 + {domain} 315 + </td> 316 + <td className="whitespace-nowrap py-4 pr-4 pl-3 text-sm font-medium text-gray-900 sm:pr-6 text-right"> 317 + <button 318 + className="rounded-md p-1.5 hover:text-green-600 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-green-600 focus:ring-offset-2 focus:ring-offset-green-50" 319 + onClick={() => { 320 + handleBanUnbaonDomain(domain, "unban"); 321 + }} 322 + > 323 + <CheckIcon className="h-5 w-5" aria-hidden="true" /> 324 + </button> 325 + </td> 326 + </tr> 327 + ); 328 + })} 329 + </tbody> 330 + </table> 331 + </div> 332 + </div> 333 + {modalAction && ( 334 + <ConfirmDomainBanModal 335 + action={modalAction} 336 + onConfirm={modalConfirm} 337 + onCancel={modalCancel} 338 + /> 339 + )} 340 + </div> 341 + ); 342 + }; 343 + 344 + export default Domains;
+116
cmd/rerelay/relay-admin-ui/src/components/Login/Login.tsx
··· 1 + import React, { useState } from "react"; 2 + import { useNavigate } from "react-router-dom"; 3 + import { RELAY_HOST } from "../../constants"; 4 + import Notification, { NotificationMeta } from "../Notification/Notification"; 5 + 6 + export default function Login() { 7 + const [token, setToken] = useState(""); 8 + const navigate = useNavigate(); 9 + 10 + // Notification Management 11 + const [shouldShowNotification, setShouldShowNotification] = 12 + useState<boolean>(false); 13 + const [notification, setNotification] = useState<NotificationMeta>({ 14 + message: "", 15 + alertType: "", 16 + }); 17 + 18 + const handleSaveToken = (e: React.FormEvent) => { 19 + e.preventDefault(); 20 + 21 + if (token) { 22 + // Try to make a request to the Admin API to verify the token 23 + fetch(`${RELAY_HOST}/admin/pds/list`, { 24 + method: "GET", 25 + headers: { 26 + "Content-Type": "application/json", 27 + Authorization: `Bearer ${token}`, 28 + }, 29 + }) 30 + .then((res) => { 31 + if (res.status !== 200) { 32 + setNotification({ 33 + message: `Failed to validate Admin Token: Status ${res.status}`, 34 + alertType: "failure", 35 + autodismiss: true, 36 + }); 37 + setShouldShowNotification(true); 38 + return; 39 + } 40 + localStorage.setItem("admin_route_token", token); 41 + setToken(""); 42 + navigate("/"); 43 + }) 44 + .catch((err) => { 45 + setNotification({ 46 + message: `Failed to validate Admin Token: ${err}`, 47 + alertType: "failure", 48 + autodismiss: true, 49 + }); 50 + setShouldShowNotification(true); 51 + }); 52 + } 53 + }; 54 + 55 + return ( 56 + <> 57 + <div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8"> 58 + <div className="sm:mx-auto sm:w-full sm:max-w-sm"> 59 + <div className="my-4"> 60 + {shouldShowNotification ? ( 61 + <Notification 62 + message={notification.message} 63 + alertType={notification.alertType} 64 + subMessage={notification.subMessage} 65 + autodismiss={notification.autodismiss} 66 + unshow={() => { 67 + setShouldShowNotification(false); 68 + setNotification({ message: "", alertType: "" }); 69 + }} 70 + show={shouldShowNotification} 71 + ></Notification> 72 + ) : ( 73 + <></> 74 + )} 75 + </div> 76 + <h2 className=" text-center text-2xl font-bold leading-9 tracking-tight text-gray-900"> 77 + Login to the Relay Admin Dashboard 78 + </h2> 79 + </div> 80 + 81 + <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> 82 + <form className="space-y-6" onSubmit={handleSaveToken}> 83 + <div> 84 + <label 85 + htmlFor="token" 86 + className="block text-sm font-medium leading-6 text-gray-900" 87 + > 88 + Access Token 89 + </label> 90 + <div className="mt-2"> 91 + <input 92 + id="token" 93 + name="token" 94 + type="password" 95 + value={token} 96 + onChange={(e) => setToken(e.target.value)} 97 + required 98 + className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" 99 + /> 100 + </div> 101 + </div> 102 + 103 + <div> 104 + <button 105 + type="submit" 106 + className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" 107 + > 108 + Set Token 109 + </button> 110 + </div> 111 + </form> 112 + </div> 113 + </div> 114 + </> 115 + ); 116 + }
+13
cmd/rerelay/relay-admin-ui/src/components/Logout/Logout.tsx
··· 1 + import { useEffect } from "react"; 2 + import { useNavigate } from "react-router-dom"; 3 + 4 + export default function Logout() { 5 + const navigate = useNavigate(); 6 + 7 + useEffect(() => { 8 + localStorage.removeItem("admin_route_token"); 9 + navigate("/login"); 10 + }, []); 11 + 12 + return <></>; 13 + }
+107
cmd/rerelay/relay-admin-ui/src/components/NewPDS/ConfirmNewPDSModal.tsx
··· 1 + import { Fragment, useState } from "react"; 2 + import { Dialog, Transition } from "@headlessui/react"; 3 + import { XCircleIcon } from "@heroicons/react/24/outline"; 4 + 5 + interface ConfirmModalProps { 6 + action: { 7 + type: "add" | "remove"; 8 + pds: string; 9 + }; 10 + onConfirm: () => void; 11 + onCancel: () => void; 12 + } 13 + 14 + const ConfirmNewPDSModal = ({ 15 + action, 16 + onConfirm, 17 + onCancel, 18 + }: ConfirmModalProps) => { 19 + const [open, setOpen] = useState(true); 20 + 21 + const handleConfirm = () => { 22 + onConfirm(); 23 + setOpen(false); 24 + }; 25 + 26 + const handleCancel = () => { 27 + onCancel(); 28 + setOpen(false); 29 + }; 30 + 31 + return ( 32 + <Transition.Root show={open} as={Fragment}> 33 + <Dialog as="div" className="relative z-10" onClose={setOpen}> 34 + <Transition.Child 35 + as={Fragment} 36 + enter="ease-out duration-300" 37 + enterFrom="opacity-0" 38 + enterTo="opacity-100" 39 + leave="ease-in duration-200" 40 + leaveFrom="opacity-100" 41 + leaveTo="opacity-0" 42 + > 43 + <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> 44 + </Transition.Child> 45 + 46 + <div className="fixed inset-0 z-10 overflow-y-auto"> 47 + <div className="flex min-h-full items-center justify-center p-4 text-center sm:items-center sm:p-0"> 48 + <Transition.Child 49 + as={Fragment} 50 + enter="ease-out duration-300" 51 + enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 52 + enterTo="opacity-100 translate-y-0 sm:scale-100" 53 + leave="ease-in duration-200" 54 + leaveFrom="opacity-100 translate-y-0 sm:scale-100" 55 + leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 56 + > 57 + <Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> 58 + <div> 59 + <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100"> 60 + <XCircleIcon 61 + className="h-6 w-6 text-yellow-600" 62 + aria-hidden="true" 63 + /> 64 + </div> 65 + <div className="mt-3 text-center sm:mt-5"> 66 + <Dialog.Title 67 + as="h3" 68 + className="text-lg font-medium leading-6 text-gray-900" 69 + > 70 + {`${action.type[0].toLocaleUpperCase()}${action.type.substring( 71 + 1 72 + )}`}{" "} 73 + PDS 74 + </Dialog.Title> 75 + <div className="mt-2"> 76 + <p className="text-sm text-gray-500"> 77 + Are you sure you want to {action.type} {action.pds}? 78 + </p> 79 + </div> 80 + </div> 81 + </div> 82 + <div className="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense"> 83 + <button 84 + type="button" 85 + className="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:col-start-2" 86 + onClick={handleConfirm} 87 + > 88 + Confirm 89 + </button> 90 + <button 91 + type="button" 92 + className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:col-start-1 sm:mt-0" 93 + onClick={handleCancel} 94 + > 95 + Cancel 96 + </button> 97 + </div> 98 + </Dialog.Panel> 99 + </Transition.Child> 100 + </div> 101 + </div> 102 + </Dialog> 103 + </Transition.Root> 104 + ); 105 + }; 106 + 107 + export default ConfirmNewPDSModal;
+243
cmd/rerelay/relay-admin-ui/src/components/NewPDS/NewPDS.tsx
··· 1 + import { FC, useEffect, useState } from "react"; 2 + import Notification, { 3 + NotificationMeta, 4 + NotificationType, 5 + } from "../Notification/Notification"; 6 + 7 + import { RELAY_HOST } from "../../constants"; 8 + 9 + import { useNavigate } from "react-router-dom"; 10 + import ConfirmNewPDSModal from "./ConfirmNewPDSModal"; 11 + import { 12 + ShieldCheckIcon, 13 + } from "@heroicons/react/24/outline"; 14 + 15 + const NewPDS: FC<{}> = () => { 16 + const [pdsHost, setPDSHost] = useState<string>(""); 17 + 18 + // Notification Management 19 + const [shouldShowNotification, setShouldShowNotification] = 20 + useState<boolean>(false); 21 + const [notification, setNotification] = useState<NotificationMeta>({ 22 + message: "", 23 + alertType: "", 24 + }); 25 + 26 + // Modal state management 27 + const [modalAction, setModalAction] = useState<{ 28 + pds: string; 29 + type: "add" | "remove"; 30 + } | null>(null); 31 + const [modalConfirm, setModalConfirm] = useState<() => void>(() => { }); 32 + const [modalCancel, setModalCancel] = useState<() => void>(() => { }); 33 + 34 + const [adminToken, setAdminToken] = useState<string>( 35 + localStorage.getItem("admin_route_token") || "" 36 + ); 37 + const navigate = useNavigate(); 38 + 39 + const setAlertWithTimeout = ( 40 + type: NotificationType, 41 + message: string, 42 + dismiss: boolean 43 + ) => { 44 + setNotification({ 45 + message, 46 + alertType: type, 47 + autodismiss: dismiss, 48 + }); 49 + setShouldShowNotification(true); 50 + }; 51 + 52 + useEffect(() => { 53 + const token = localStorage.getItem("admin_route_token"); 54 + if (token) { 55 + setAdminToken(token); 56 + } else { 57 + navigate("/login"); 58 + } 59 + }, []); 60 + 61 + const requestAddPDS = (pds: string) => { 62 + fetch(`${RELAY_HOST}/admin/pds/requestCrawl`, { 63 + method: "POST", 64 + headers: { 65 + "Content-Type": "application/json", 66 + Authorization: `Bearer ${adminToken}`, 67 + }, 68 + body: JSON.stringify({ 69 + hostname: pds, 70 + }), 71 + }) 72 + .then((res) => { 73 + if (res.status !== 200) { 74 + try { 75 + res.json().then((data) => { 76 + if (data.error) { 77 + setAlertWithTimeout( 78 + "failure", 79 + `Failed to add PDS: ${data.error}`, 80 + true 81 + ); 82 + } else { 83 + setAlertWithTimeout( 84 + "failure", 85 + `Failed to add PDS: ${res.statusText}`, 86 + true 87 + ); 88 + } 89 + }); 90 + } 91 + catch (err) { 92 + setAlertWithTimeout( 93 + "failure", 94 + `Failed to add PDS: ${err}`, 95 + true 96 + ); 97 + } 98 + } else { 99 + try { 100 + res.json().then((data) => { 101 + if (data.error) { 102 + setAlertWithTimeout( 103 + "failure", 104 + `Failed to add PDS: ${data.error}`, 105 + true 106 + ); 107 + } else { 108 + setAlertWithTimeout( 109 + "success", 110 + `Successfully added PDS ${pds}`, 111 + true 112 + ); 113 + } 114 + }); 115 + } catch (err) { 116 + setAlertWithTimeout( 117 + "failure", 118 + `Failed to add PDS: ${err}`, 119 + true 120 + ); 121 + } 122 + } 123 + }) 124 + .catch((err) => { 125 + setAlertWithTimeout("failure", `Failed to add PDS: ${err}`, true); 126 + }); 127 + }; 128 + 129 + const requestRemovePDS = (pds: string) => { 130 + setAlertWithTimeout( 131 + "failure", 132 + `Failed to remove PDS: ${pds} - Not implemented`, 133 + true 134 + ); 135 + }; 136 + 137 + const handleAddPDS = ( 138 + pds: string, 139 + type: "add" | "remove" 140 + ) => { 141 + if (pds === "") { 142 + setAlertWithTimeout("failure", "PDS Hostname cannot be empty", true); 143 + return; 144 + } 145 + 146 + // Strip the protocol from the hostname 147 + pds = pds.replace(/^https?:\/\//, ""); 148 + 149 + setModalAction({ pds: pds, type }); 150 + 151 + setModalConfirm(() => { 152 + return () => { 153 + if (type === "add") requestAddPDS(pds); 154 + else requestRemovePDS(pds); 155 + 156 + setModalAction(null); 157 + }; 158 + }); 159 + 160 + setModalCancel(() => { 161 + return () => { 162 + setModalAction(null); 163 + }; 164 + }); 165 + }; 166 + 167 + return ( 168 + <div className="mx-auto max-w-full"> 169 + {shouldShowNotification ? ( 170 + <Notification 171 + message={notification.message} 172 + alertType={notification.alertType} 173 + subMessage={notification.subMessage} 174 + autodismiss={notification.autodismiss} 175 + unshow={() => { 176 + setShouldShowNotification(false); 177 + setNotification({ message: "", alertType: "" }); 178 + }} 179 + show={shouldShowNotification} 180 + ></Notification> 181 + ) : ( 182 + <></> 183 + )} 184 + <div className="sm:flex sm:items-center"> 185 + <div className="sm:flex-auto"> 186 + <h1 className="text-2xl font-semibold leading-6 text-gray-900"> 187 + Add a PDS 188 + </h1> 189 + <p className="mt-2 text-sm text-gray-700"> 190 + Add a PDS to the Relay and trigger crawling. 191 + </p> 192 + </div> 193 + </div> 194 + <div className="flex-grow mt-5"> 195 + <div className="max-w-3xl w-full"> 196 + <label 197 + htmlFor="email" 198 + className="block text-sm font-medium leading-6 text-gray-900" 199 + > 200 + PDS Hostname 201 + </label> 202 + <div className="mt-2 inline-flex flex-col sm:flex-row"> 203 + <input 204 + type="text" 205 + name="pds" 206 + id="pds" 207 + className="block w-72 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" 208 + placeholder="hydnum.us-west.host.bsky.network" 209 + value={pdsHost} 210 + onChange={(e) => { 211 + setPDSHost(e.target.value); 212 + }} 213 + /> 214 + <div className="inline-flex mt-4 sm:mt-0"> 215 + <button 216 + type="button" 217 + onClick={() => { 218 + handleAddPDS(pdsHost.trim(), "add"); 219 + }} 220 + className="ml-0 sm:ml-2 inline-flex whitespace-nowrap items-center gap-x-1.5 rounded-md bg-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" 221 + > 222 + <ShieldCheckIcon 223 + className="-ml-0.5 h-5 w-5" 224 + aria-hidden="true" 225 + /> 226 + Add PDS 227 + </button> 228 + </div> 229 + </div> 230 + </div> 231 + </div> 232 + {modalAction && ( 233 + <ConfirmNewPDSModal 234 + action={modalAction} 235 + onConfirm={modalConfirm} 236 + onCancel={modalCancel} 237 + /> 238 + )} 239 + </div> 240 + ); 241 + }; 242 + 243 + export default NewPDS;
+127
cmd/rerelay/relay-admin-ui/src/components/Notification/Notification.tsx
··· 1 + import { Fragment, useEffect } from "react"; 2 + import { Transition } from "@headlessui/react"; 3 + import { 4 + ArrowUpCircleIcon, 5 + ArrowDownCircleIcon, 6 + CheckCircleIcon, 7 + XCircleIcon, 8 + } from "@heroicons/react/24/outline"; 9 + import { XMarkIcon } from "@heroicons/react/24/solid"; 10 + 11 + interface NotificationProps { 12 + alertType: string; 13 + message: string; 14 + subMessage?: string; 15 + component?: JSX.Element; 16 + show: boolean; 17 + unshow: Function; 18 + autodismiss?: boolean; 19 + } 20 + 21 + export interface NotificationMeta { 22 + message: string; 23 + subMessage?: string; 24 + alertType: string; 25 + autodismiss?: boolean; 26 + } 27 + 28 + export type NotificationType = "success" | "failure" | "loading" | "uploading"; 29 + 30 + export default function Notification(props: NotificationProps) { 31 + useEffect(() => { 32 + if (props.autodismiss) { 33 + setTimeout(() => { 34 + props.unshow(); 35 + }, 3500); 36 + } 37 + }, []); 38 + 39 + return ( 40 + <> 41 + {/* Global notification live region, render this permanently at the end of the document */} 42 + <div 43 + aria-live="assertive" 44 + className="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:p-6 sm:items-start z-20" 45 + > 46 + <div className="w-full flex flex-col items-center space-y-4 sm:items-end mr-10"> 47 + {/* Notification panel, dynamically insert this into the live region when it needs to be displayed */} 48 + <Transition 49 + show={props.show} 50 + as={Fragment} 51 + enter="transform ease-out duration-300 transition" 52 + enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2" 53 + enterTo="translate-y-0 opacity-100 sm:translate-x-0" 54 + leave="transition ease-in duration-100" 55 + leaveFrom="opacity-100" 56 + leaveTo="opacity-0" 57 + > 58 + <div className="max-w-md w-full bg-white shadow-lg rounded-lg pointer-events-auto ring-1 ring-black ring-opacity-5 overflow-hidden"> 59 + <div className="p-4"> 60 + <div className="flex items-start"> 61 + <div className="flex-shrink-0"> 62 + {props.alertType === "success" ? ( 63 + <CheckCircleIcon 64 + className="h-6 w-6 text-green-400" 65 + aria-hidden="true" 66 + /> 67 + ) : ( 68 + <></> 69 + )} 70 + {props.alertType === "failure" ? ( 71 + <XCircleIcon 72 + className="h-6 w-6 text-red-400" 73 + aria-hidden="true" 74 + /> 75 + ) : ( 76 + <></> 77 + )} 78 + {props.alertType === "loading" ? ( 79 + <ArrowDownCircleIcon 80 + className="h-6 w-6 text-blue-400" 81 + aria-hidden="true" 82 + /> 83 + ) : ( 84 + <></> 85 + )} 86 + {props.alertType === "uploading" ? ( 87 + <ArrowUpCircleIcon 88 + className="h-6 w-6 text-blue-400" 89 + aria-hidden="true" 90 + /> 91 + ) : ( 92 + <></> 93 + )} 94 + </div> 95 + <div className="ml-3 w-0 flex-1 pt-0.5"> 96 + <p className="text-sm font-medium text-gray-900"> 97 + {props.message} 98 + </p> 99 + {props.subMessage ? ( 100 + <p className="mt-1 text-sm text-gray-500"> 101 + {props.subMessage} 102 + </p> 103 + ) : ( 104 + <></> 105 + )} 106 + {props.component ? props.component : <></>} 107 + </div> 108 + <div className="ml-4 flex-shrink-0 flex"> 109 + <button 110 + className="bg-white rounded-md inline-flex text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" 111 + onClick={() => { 112 + props.unshow(); 113 + }} 114 + > 115 + <span className="sr-only">Close</span> 116 + <XMarkIcon className="h-5 w-5" aria-hidden="true" /> 117 + </button> 118 + </div> 119 + </div> 120 + </div> 121 + </div> 122 + </Transition> 123 + </div> 124 + </div> 125 + </> 126 + ); 127 + }
+107
cmd/rerelay/relay-admin-ui/src/components/Repos/ConfirmRepoTakedownModal.tsx
··· 1 + import { Fragment, useState } from "react"; 2 + import { Dialog, Transition } from "@headlessui/react"; 3 + import { XCircleIcon } from "@heroicons/react/24/outline"; 4 + 5 + interface ConfirmModalProps { 6 + action: { 7 + type: "takedown" | "untakedown"; 8 + repo: string; 9 + }; 10 + onConfirm: () => void; 11 + onCancel: () => void; 12 + } 13 + 14 + const ConfirmRepoTakedownModal = ({ 15 + action, 16 + onConfirm, 17 + onCancel, 18 + }: ConfirmModalProps) => { 19 + const [open, setOpen] = useState(true); 20 + 21 + const handleConfirm = () => { 22 + onConfirm(); 23 + setOpen(false); 24 + }; 25 + 26 + const handleCancel = () => { 27 + onCancel(); 28 + setOpen(false); 29 + }; 30 + 31 + return ( 32 + <Transition.Root show={open} as={Fragment}> 33 + <Dialog as="div" className="relative z-10" onClose={setOpen}> 34 + <Transition.Child 35 + as={Fragment} 36 + enter="ease-out duration-300" 37 + enterFrom="opacity-0" 38 + enterTo="opacity-100" 39 + leave="ease-in duration-200" 40 + leaveFrom="opacity-100" 41 + leaveTo="opacity-0" 42 + > 43 + <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" /> 44 + </Transition.Child> 45 + 46 + <div className="fixed inset-0 z-10 overflow-y-auto"> 47 + <div className="flex min-h-full items-center justify-center p-4 text-center sm:items-center sm:p-0"> 48 + <Transition.Child 49 + as={Fragment} 50 + enter="ease-out duration-300" 51 + enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 52 + enterTo="opacity-100 translate-y-0 sm:scale-100" 53 + leave="ease-in duration-200" 54 + leaveFrom="opacity-100 translate-y-0 sm:scale-100" 55 + leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" 56 + > 57 + <Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-white px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"> 58 + <div> 59 + <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-yellow-100"> 60 + <XCircleIcon 61 + className="h-6 w-6 text-yellow-600" 62 + aria-hidden="true" 63 + /> 64 + </div> 65 + <div className="mt-3 text-center sm:mt-5"> 66 + <Dialog.Title 67 + as="h3" 68 + className="text-lg font-medium leading-6 text-gray-900" 69 + > 70 + {`${action.type[0].toLocaleUpperCase()}${action.type.substring( 71 + 1 72 + )}`}{" "} 73 + Repo 74 + </Dialog.Title> 75 + <div className="mt-2"> 76 + <p className="text-sm text-gray-500"> 77 + Are you sure you want to {action.type} {action.repo}? 78 + </p> 79 + </div> 80 + </div> 81 + </div> 82 + <div className="mt-5 sm:mt-6 sm:grid sm:grid-cols-2 sm:gap-3 sm:grid-flow-row-dense"> 83 + <button 84 + type="button" 85 + className="inline-flex w-full justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:col-start-2" 86 + onClick={handleConfirm} 87 + > 88 + Confirm 89 + </button> 90 + <button 91 + type="button" 92 + className="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 sm:col-start-1 sm:mt-0" 93 + onClick={handleCancel} 94 + > 95 + Cancel 96 + </button> 97 + </div> 98 + </Dialog.Panel> 99 + </Transition.Child> 100 + </div> 101 + </div> 102 + </Dialog> 103 + </Transition.Root> 104 + ); 105 + }; 106 + 107 + export default ConfirmRepoTakedownModal;
+243
cmd/rerelay/relay-admin-ui/src/components/Repos/Repos.tsx
··· 1 + import { FC, useEffect, useState } from "react"; 2 + import Notification, { 3 + NotificationMeta, 4 + NotificationType, 5 + } from "../Notification/Notification"; 6 + 7 + import { RELAY_HOST } from "../../constants"; 8 + 9 + import { useNavigate } from "react-router-dom"; 10 + import ConfirmRepoTakedownModal from "./ConfirmRepoTakedownModal"; 11 + import { 12 + ShieldCheckIcon, 13 + ShieldExclamationIcon, 14 + } from "@heroicons/react/24/outline"; 15 + 16 + const Repos: FC<{}> = () => { 17 + const [repoToTakedown, setRepoToTakedown] = useState<string>(""); 18 + 19 + // Notification Management 20 + const [shouldShowNotification, setShouldShowNotification] = 21 + useState<boolean>(false); 22 + const [notification, setNotification] = useState<NotificationMeta>({ 23 + message: "", 24 + alertType: "", 25 + }); 26 + 27 + // Modal state management 28 + const [modalAction, setModalAction] = useState<{ 29 + repo: string; 30 + type: "takedown" | "untakedown"; 31 + } | null>(null); 32 + const [modalConfirm, setModalConfirm] = useState<() => void>(() => { }); 33 + const [modalCancel, setModalCancel] = useState<() => void>(() => { }); 34 + 35 + const [adminToken, setAdminToken] = useState<string>( 36 + localStorage.getItem("admin_route_token") || "" 37 + ); 38 + const navigate = useNavigate(); 39 + 40 + const setAlertWithTimeout = ( 41 + type: NotificationType, 42 + message: string, 43 + dismiss: boolean 44 + ) => { 45 + setNotification({ 46 + message, 47 + alertType: type, 48 + autodismiss: dismiss, 49 + }); 50 + setShouldShowNotification(true); 51 + }; 52 + 53 + useEffect(() => { 54 + const token = localStorage.getItem("admin_route_token"); 55 + if (token) { 56 + setAdminToken(token); 57 + } else { 58 + navigate("/login"); 59 + } 60 + }, []); 61 + 62 + const requestTakedownRepo = (repo: string) => { 63 + fetch(`${RELAY_HOST}/admin/repo/takeDown`, { 64 + method: "POST", 65 + headers: { 66 + "Content-Type": "application/json", 67 + Authorization: `Bearer ${adminToken}`, 68 + }, 69 + body: JSON.stringify({ 70 + did: repo, 71 + }), 72 + }) 73 + .then((res) => res.json()) 74 + .then((res) => { 75 + if (res.error) { 76 + setAlertWithTimeout( 77 + "failure", 78 + `Failed to takedown repo: ${res.error}`, 79 + true 80 + ); 81 + } else { 82 + setAlertWithTimeout( 83 + "success", 84 + `Successfully tookdown repo ${repo}`, 85 + true 86 + ); 87 + } 88 + }) 89 + .catch((err) => { 90 + setAlertWithTimeout("failure", `Failed to takedown repo: ${err}`, true); 91 + }); 92 + }; 93 + 94 + const requestUntakedownRepo = (repo: string) => { 95 + fetch(`${RELAY_HOST}/admin/repo/reverseTakedown`, { 96 + method: "POST", 97 + headers: { 98 + "Content-Type": "application/json", 99 + Authorization: `Bearer ${adminToken}`, 100 + }, 101 + body: JSON.stringify({ 102 + did: repo, 103 + }), 104 + }) 105 + .then((res) => res.json()) 106 + 107 + .then((res) => { 108 + if (res.error !== 200) { 109 + setAlertWithTimeout( 110 + "failure", 111 + `Failed to untakedown repo: ${res.error}`, 112 + true 113 + ); 114 + } else { 115 + setAlertWithTimeout( 116 + "success", 117 + `Successfully untookdown repo ${repo}`, 118 + true 119 + ); 120 + } 121 + }) 122 + .catch((err) => { 123 + setAlertWithTimeout( 124 + "failure", 125 + `Failed to untakedown repo: ${err}`, 126 + true 127 + ); 128 + }); 129 + }; 130 + 131 + const handleTakedownRepo = ( 132 + repo: string, 133 + type: "takedown" | "untakedown" 134 + ) => { 135 + setModalAction({ repo: repo, type }); 136 + 137 + setModalConfirm(() => { 138 + return () => { 139 + if (type === "takedown") requestTakedownRepo(repo); 140 + else requestUntakedownRepo(repo); 141 + 142 + setModalAction(null); 143 + }; 144 + }); 145 + 146 + setModalCancel(() => { 147 + return () => { 148 + setModalAction(null); 149 + }; 150 + }); 151 + }; 152 + 153 + return ( 154 + <div className="mx-auto max-w-full"> 155 + {shouldShowNotification ? ( 156 + <Notification 157 + message={notification.message} 158 + alertType={notification.alertType} 159 + subMessage={notification.subMessage} 160 + autodismiss={notification.autodismiss} 161 + unshow={() => { 162 + setShouldShowNotification(false); 163 + setNotification({ message: "", alertType: "" }); 164 + }} 165 + show={shouldShowNotification} 166 + ></Notification> 167 + ) : ( 168 + <></> 169 + )} 170 + <div className="sm:flex sm:items-center"> 171 + <div className="sm:flex-auto"> 172 + <h1 className="text-2xl font-semibold leading-6 text-gray-900"> 173 + Repo Takedowns 174 + </h1> 175 + <p className="mt-2 text-sm text-gray-700"> 176 + Takedown a repo to purge it from the Relay history and reject all 177 + future events for it. 178 + </p> 179 + </div> 180 + </div> 181 + <div className="flex-grow mt-5"> 182 + <div className="max-w-3xl w-full"> 183 + <label 184 + htmlFor="email" 185 + className="block text-sm font-medium leading-6 text-gray-900" 186 + > 187 + Repo DID 188 + </label> 189 + <div className="mt-2 inline-flex flex-col sm:flex-row"> 190 + <input 191 + type="text" 192 + name="repo" 193 + id="repo" 194 + className="block w-72 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" 195 + placeholder="did:plc:abadperson" 196 + value={repoToTakedown} 197 + onChange={(e) => { 198 + setRepoToTakedown(e.target.value); 199 + }} 200 + /> 201 + <div className="inline-flex mt-4 sm:mt-0"> 202 + <button 203 + type="button" 204 + onClick={() => { 205 + handleTakedownRepo(repoToTakedown.trim(), "takedown"); 206 + }} 207 + className="ml-0 sm:ml-2 inline-flex whitespace-nowrap items-center gap-x-1.5 rounded-md bg-red-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600" 208 + > 209 + <ShieldExclamationIcon 210 + className="-ml-0.5 h-5 w-5" 211 + aria-hidden="true" 212 + /> 213 + Takedown Repo 214 + </button> 215 + <button 216 + type="button" 217 + onClick={() => { 218 + handleTakedownRepo(repoToTakedown.trim(), "untakedown"); 219 + }} 220 + className="ml-2 inline-flex whitespace-nowrap items-center gap-x-1.5 rounded-md bg-green-600 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-green-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-600" 221 + > 222 + <ShieldCheckIcon 223 + className="-ml-0.5 h-5 w-5" 224 + aria-hidden="true" 225 + /> 226 + Untakedown Repo 227 + </button> 228 + </div> 229 + </div> 230 + </div> 231 + </div> 232 + {modalAction && ( 233 + <ConfirmRepoTakedownModal 234 + action={modalAction} 235 + onConfirm={modalConfirm} 236 + onCancel={modalCancel} 237 + /> 238 + )} 239 + </div> 240 + ); 241 + }; 242 + 243 + export default Repos;
+9
cmd/rerelay/relay-admin-ui/src/constants.ts
··· 1 + const isDev = 2 + window.location.hostname === "localhost" || 3 + window.location.hostname === "jaz1"; 4 + 5 + const RELAY_HOST = `${window.location.protocol}//${window.location.hostname}:${ 6 + isDev ? "2470" : window.location.port 7 + }`; 8 + 9 + export { RELAY_HOST };
+3
cmd/rerelay/relay-admin-ui/src/index.css
··· 1 + @tailwind base; 2 + @tailwind components; 3 + @tailwind utilities;
+10
cmd/rerelay/relay-admin-ui/src/main.tsx
··· 1 + import React from "react"; 2 + import ReactDOM from "react-dom/client"; 3 + import App from "./App.tsx"; 4 + import "./index.css"; 5 + 6 + ReactDOM.createRoot(document.getElementById("root")!).render( 7 + <React.StrictMode> 8 + <App /> 9 + </React.StrictMode> 10 + );
+19
cmd/rerelay/relay-admin-ui/src/models/consumer.ts
··· 1 + interface Consumer { 2 + RemoteAddr: string; 3 + UserAgent: string; 4 + EventsConsumed: number; 5 + ConnectedAt: Date; 6 + ID: number; 7 + } 8 + 9 + interface ConsumerResponse { 10 + id: number; 11 + remote_addr: string; 12 + user_agent: string; 13 + events_consumed: number; 14 + connected_at: string; 15 + } 16 + 17 + type ConsumerKey = keyof Consumer; 18 + 19 + export type { Consumer, ConsumerResponse, ConsumerKey };
+28
cmd/rerelay/relay-admin-ui/src/models/pds.ts
··· 1 + interface RateLimit { 2 + Max: number; 3 + WindowSeconds: number; 4 + } 5 + 6 + interface PDS { 7 + ID: number; 8 + CreatedAt: string; 9 + UpdatedAt: string; 10 + DeletedAt: any; 11 + Host: string; 12 + Did: string; 13 + SSL: boolean; 14 + Cursor: number; 15 + Registered: boolean; 16 + Blocked: boolean; 17 + HasActiveConnection: boolean; 18 + EventsSeenSinceStartup?: number; 19 + PerSecondEventRate: RateLimit; 20 + PerHourEventRate: RateLimit; 21 + PerDayEventRate: RateLimit; 22 + RepoCount: number; 23 + RepoLimit: number; 24 + } 25 + 26 + type PDSKey = keyof PDS; 27 + 28 + export type { PDS, PDSKey };
+1
cmd/rerelay/relay-admin-ui/src/vite-env.d.ts
··· 1 + /// <reference types="vite/client" />
+13
cmd/rerelay/relay-admin-ui/tailwind.config.js
··· 1 + /** @type {import('tailwindcss').Config} */ 2 + export default { 3 + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 + theme: { 5 + extend: { 6 + screens: { 7 + tall: { raw: "(min-height: 800px)" }, 8 + // => @media (min-height: 800px) { ... } 9 + }, 10 + }, 11 + }, 12 + plugins: [require("@tailwindcss/forms")], 13 + };
+25
cmd/rerelay/relay-admin-ui/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "ES2020", 4 + "useDefineForClassFields": true, 5 + "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 + "module": "ESNext", 7 + "skipLibCheck": true, 8 + 9 + /* Bundler mode */ 10 + "moduleResolution": "bundler", 11 + "allowImportingTsExtensions": true, 12 + "resolveJsonModule": true, 13 + "isolatedModules": true, 14 + "noEmit": true, 15 + "jsx": "react-jsx", 16 + 17 + /* Linting */ 18 + "strict": true, 19 + "noUnusedLocals": true, 20 + "noUnusedParameters": true, 21 + "noFallthroughCasesInSwitch": true 22 + }, 23 + "include": ["src"], 24 + "references": [{ "path": "./tsconfig.node.json" }] 25 + }
+10
cmd/rerelay/relay-admin-ui/tsconfig.node.json
··· 1 + { 2 + "compilerOptions": { 3 + "composite": true, 4 + "skipLibCheck": true, 5 + "module": "ESNext", 6 + "moduleResolution": "bundler", 7 + "allowSyntheticDefaultImports": true 8 + }, 9 + "include": ["vite.config.ts"] 10 + }
+7
cmd/rerelay/relay-admin-ui/vite.config.ts
··· 1 + import { defineConfig } from 'vite' 2 + import react from '@vitejs/plugin-react' 3 + 4 + // https://vitejs.dev/config/ 5 + export default defineConfig({ 6 + plugins: [react()], 7 + })
+1910
cmd/rerelay/relay-admin-ui/yarn.lock
··· 1 + # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 + # yarn lockfile v1 3 + 4 + 5 + "@aashutoshrathi/word-wrap@^1.2.3": 6 + version "1.2.6" 7 + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" 8 + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== 9 + 10 + "@alloc/quick-lru@^5.2.0": 11 + version "5.2.0" 12 + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" 13 + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== 14 + 15 + "@ampproject/remapping@^2.2.0": 16 + version "2.2.1" 17 + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" 18 + integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg== 19 + dependencies: 20 + "@jridgewell/gen-mapping" "^0.3.0" 21 + "@jridgewell/trace-mapping" "^0.3.9" 22 + 23 + "@babel/code-frame@^7.22.5": 24 + version "7.22.5" 25 + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658" 26 + integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ== 27 + dependencies: 28 + "@babel/highlight" "^7.22.5" 29 + 30 + "@babel/compat-data@^7.22.9": 31 + version "7.22.9" 32 + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" 33 + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== 34 + 35 + "@babel/core@^7.22.5": 36 + version "7.22.9" 37 + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" 38 + integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== 39 + dependencies: 40 + "@ampproject/remapping" "^2.2.0" 41 + "@babel/code-frame" "^7.22.5" 42 + "@babel/generator" "^7.22.9" 43 + "@babel/helper-compilation-targets" "^7.22.9" 44 + "@babel/helper-module-transforms" "^7.22.9" 45 + "@babel/helpers" "^7.22.6" 46 + "@babel/parser" "^7.22.7" 47 + "@babel/template" "^7.22.5" 48 + "@babel/traverse" "^7.22.8" 49 + "@babel/types" "^7.22.5" 50 + convert-source-map "^1.7.0" 51 + debug "^4.1.0" 52 + gensync "^1.0.0-beta.2" 53 + json5 "^2.2.2" 54 + semver "^6.3.1" 55 + 56 + "@babel/generator@^7.22.7", "@babel/generator@^7.22.9": 57 + version "7.22.9" 58 + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" 59 + integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== 60 + dependencies: 61 + "@babel/types" "^7.22.5" 62 + "@jridgewell/gen-mapping" "^0.3.2" 63 + "@jridgewell/trace-mapping" "^0.3.17" 64 + jsesc "^2.5.1" 65 + 66 + "@babel/helper-compilation-targets@^7.22.9": 67 + version "7.22.9" 68 + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" 69 + integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== 70 + dependencies: 71 + "@babel/compat-data" "^7.22.9" 72 + "@babel/helper-validator-option" "^7.22.5" 73 + browserslist "^4.21.9" 74 + lru-cache "^5.1.1" 75 + semver "^6.3.1" 76 + 77 + "@babel/helper-environment-visitor@^7.22.5": 78 + version "7.22.5" 79 + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98" 80 + integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q== 81 + 82 + "@babel/helper-function-name@^7.22.5": 83 + version "7.22.5" 84 + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be" 85 + integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ== 86 + dependencies: 87 + "@babel/template" "^7.22.5" 88 + "@babel/types" "^7.22.5" 89 + 90 + "@babel/helper-hoist-variables@^7.22.5": 91 + version "7.22.5" 92 + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" 93 + integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== 94 + dependencies: 95 + "@babel/types" "^7.22.5" 96 + 97 + "@babel/helper-module-imports@^7.22.5": 98 + version "7.22.5" 99 + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" 100 + integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== 101 + dependencies: 102 + "@babel/types" "^7.22.5" 103 + 104 + "@babel/helper-module-transforms@^7.22.9": 105 + version "7.22.9" 106 + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" 107 + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== 108 + dependencies: 109 + "@babel/helper-environment-visitor" "^7.22.5" 110 + "@babel/helper-module-imports" "^7.22.5" 111 + "@babel/helper-simple-access" "^7.22.5" 112 + "@babel/helper-split-export-declaration" "^7.22.6" 113 + "@babel/helper-validator-identifier" "^7.22.5" 114 + 115 + "@babel/helper-plugin-utils@^7.22.5": 116 + version "7.22.5" 117 + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" 118 + integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== 119 + 120 + "@babel/helper-simple-access@^7.22.5": 121 + version "7.22.5" 122 + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" 123 + integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== 124 + dependencies: 125 + "@babel/types" "^7.22.5" 126 + 127 + "@babel/helper-split-export-declaration@^7.22.6": 128 + version "7.22.6" 129 + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" 130 + integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== 131 + dependencies: 132 + "@babel/types" "^7.22.5" 133 + 134 + "@babel/helper-string-parser@^7.22.5": 135 + version "7.22.5" 136 + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" 137 + integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== 138 + 139 + "@babel/helper-validator-identifier@^7.22.5": 140 + version "7.22.5" 141 + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" 142 + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== 143 + 144 + "@babel/helper-validator-option@^7.22.5": 145 + version "7.22.5" 146 + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" 147 + integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== 148 + 149 + "@babel/helpers@^7.22.6": 150 + version "7.22.6" 151 + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd" 152 + integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA== 153 + dependencies: 154 + "@babel/template" "^7.22.5" 155 + "@babel/traverse" "^7.22.6" 156 + "@babel/types" "^7.22.5" 157 + 158 + "@babel/highlight@^7.22.5": 159 + version "7.22.5" 160 + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031" 161 + integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw== 162 + dependencies: 163 + "@babel/helper-validator-identifier" "^7.22.5" 164 + chalk "^2.0.0" 165 + js-tokens "^4.0.0" 166 + 167 + "@babel/parser@^7.22.5", "@babel/parser@^7.22.7": 168 + version "7.22.7" 169 + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae" 170 + integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q== 171 + 172 + "@babel/plugin-transform-react-jsx-self@^7.22.5": 173 + version "7.22.5" 174 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.22.5.tgz#ca2fdc11bc20d4d46de01137318b13d04e481d8e" 175 + integrity sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g== 176 + dependencies: 177 + "@babel/helper-plugin-utils" "^7.22.5" 178 + 179 + "@babel/plugin-transform-react-jsx-source@^7.22.5": 180 + version "7.22.5" 181 + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.22.5.tgz#49af1615bfdf6ed9d3e9e43e425e0b2b65d15b6c" 182 + integrity sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w== 183 + dependencies: 184 + "@babel/helper-plugin-utils" "^7.22.5" 185 + 186 + "@babel/template@^7.22.5": 187 + version "7.22.5" 188 + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec" 189 + integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw== 190 + dependencies: 191 + "@babel/code-frame" "^7.22.5" 192 + "@babel/parser" "^7.22.5" 193 + "@babel/types" "^7.22.5" 194 + 195 + "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8": 196 + version "7.22.8" 197 + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" 198 + integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== 199 + dependencies: 200 + "@babel/code-frame" "^7.22.5" 201 + "@babel/generator" "^7.22.7" 202 + "@babel/helper-environment-visitor" "^7.22.5" 203 + "@babel/helper-function-name" "^7.22.5" 204 + "@babel/helper-hoist-variables" "^7.22.5" 205 + "@babel/helper-split-export-declaration" "^7.22.6" 206 + "@babel/parser" "^7.22.7" 207 + "@babel/types" "^7.22.5" 208 + debug "^4.1.0" 209 + globals "^11.1.0" 210 + 211 + "@babel/types@^7.22.5": 212 + version "7.22.5" 213 + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe" 214 + integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA== 215 + dependencies: 216 + "@babel/helper-string-parser" "^7.22.5" 217 + "@babel/helper-validator-identifier" "^7.22.5" 218 + to-fast-properties "^2.0.0" 219 + 220 + "@esbuild/android-arm64@0.18.14": 221 + version "0.18.14" 222 + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.14.tgz#d86197e6ff965a187b2ea2704915f596a040ed4b" 223 + integrity sha512-rZ2v+Luba5/3D6l8kofWgTnqE+qsC/L5MleKIKFyllHTKHrNBMqeRCnZI1BtRx8B24xMYxeU32iIddRQqMsOsg== 224 + 225 + "@esbuild/android-arm@0.18.14": 226 + version "0.18.14" 227 + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.14.tgz#ed59310c0e6ec6df8b17e363d33a954ecf870f4f" 228 + integrity sha512-blODaaL+lngG5bdK/t4qZcQvq2BBqrABmYwqPPcS5VRxrCSGHb9R/rA3fqxh7R18I7WU4KKv+NYkt22FDfalcg== 229 + 230 + "@esbuild/android-x64@0.18.14": 231 + version "0.18.14" 232 + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.14.tgz#e01b387f1db3dd2596a44e8c577aa2609750bc82" 233 + integrity sha512-qSwh8y38QKl+1Iqg+YhvCVYlSk3dVLk9N88VO71U4FUjtiSFylMWK3Ugr8GC6eTkkP4Tc83dVppt2n8vIdlSGg== 234 + 235 + "@esbuild/darwin-arm64@0.18.14": 236 + version "0.18.14" 237 + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.14.tgz#e92fbdeb9ff209a762cf107df3026c1b3e04ab85" 238 + integrity sha512-9Hl2D2PBeDYZiNbnRKRWuxwHa9v5ssWBBjisXFkVcSP5cZqzZRFBUWEQuqBHO4+PKx4q4wgHoWtfQ1S7rUqJ2Q== 239 + 240 + "@esbuild/darwin-x64@0.18.14": 241 + version "0.18.14" 242 + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.14.tgz#bc1884d9f812647e2078fa4c46e4bffec53c7c09" 243 + integrity sha512-ZnI3Dg4ElQ6tlv82qLc/UNHtFsgZSKZ7KjsUNAo1BF1SoYDjkGKHJyCrYyWjFecmXpvvG/KJ9A/oe0H12odPLQ== 244 + 245 + "@esbuild/freebsd-arm64@0.18.14": 246 + version "0.18.14" 247 + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.14.tgz#1fa876f627536b5037f4aed90545ccc330fd509b" 248 + integrity sha512-h3OqR80Da4oQCIa37zl8tU5MwHQ7qgPV0oVScPfKJK21fSRZEhLE4IIVpmcOxfAVmqjU6NDxcxhYaM8aDIGRLw== 249 + 250 + "@esbuild/freebsd-x64@0.18.14": 251 + version "0.18.14" 252 + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.14.tgz#effaa4c5d7bab695b5e6fae459eaf49121fbc7c3" 253 + integrity sha512-ha4BX+S6CZG4BoH9tOZTrFIYC1DH13UTCRHzFc3GWX74nz3h/N6MPF3tuR3XlsNjMFUazGgm35MPW5tHkn2lzQ== 254 + 255 + "@esbuild/linux-arm64@0.18.14": 256 + version "0.18.14" 257 + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.14.tgz#24bb4b1836fe7900e1ffda78aa6875a4eb500e3a" 258 + integrity sha512-IXORRe22In7U65NZCzjwAUc03nn8SDIzWCnfzJ6t/8AvGx5zBkcLfknI+0P+hhuftufJBmIXxdSTbzWc8X/V4w== 259 + 260 + "@esbuild/linux-arm@0.18.14": 261 + version "0.18.14" 262 + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.14.tgz#7f3490320a4627f4c850a8613385bdf3ffb82285" 263 + integrity sha512-5+7vehI1iqru5WRtJyU2XvTOvTGURw3OZxe3YTdE9muNNIdmKAVmSHpB3Vw2LazJk2ifEdIMt/wTWnVe5V98Kg== 264 + 265 + "@esbuild/linux-ia32@0.18.14": 266 + version "0.18.14" 267 + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.14.tgz#91f1e82f92ffaff8d72f9d90a0f209022529031a" 268 + integrity sha512-BfHlMa0nibwpjG+VXbOoqJDmFde4UK2gnW351SQ2Zd4t1N3zNdmUEqRkw/srC1Sa1DRBE88Dbwg4JgWCbNz/FQ== 269 + 270 + "@esbuild/linux-loong64@0.18.14": 271 + version "0.18.14" 272 + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.14.tgz#cd5cb806af6361578800af79919c5cfd53401a17" 273 + integrity sha512-j2/Ex++DRUWIAaUDprXd3JevzGtZ4/d7VKz+AYDoHZ3HjJzCyYBub9CU1wwIXN+viOP0b4VR3RhGClsvyt/xSw== 274 + 275 + "@esbuild/linux-mips64el@0.18.14": 276 + version "0.18.14" 277 + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.14.tgz#c635b6c0b8b4f9b4bff3aaafad59fa8cc07b354a" 278 + integrity sha512-qn2+nc+ZCrJmiicoAnJXJJkZWt8Nwswgu1crY7N+PBR8ChBHh89XRxj38UU6Dkthl2yCVO9jWuafZ24muzDC/A== 279 + 280 + "@esbuild/linux-ppc64@0.18.14": 281 + version "0.18.14" 282 + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.14.tgz#9b2bb80b7e30667a81ffbcddb74ad8e79330cc94" 283 + integrity sha512-aGzXzd+djqeEC5IRkDKt3kWzvXoXC6K6GyYKxd+wsFJ2VQYnOWE954qV2tvy5/aaNrmgPTb52cSCHFE+Z7Z0yg== 284 + 285 + "@esbuild/linux-riscv64@0.18.14": 286 + version "0.18.14" 287 + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.14.tgz#9520d34a4ecbf404e56f4cebdcc686c83b66babc" 288 + integrity sha512-8C6vWbfr0ygbAiMFLS6OPz0BHvApkT2gCboOGV76YrYw+sD/MQJzyITNsjZWDXJwPu9tjrFQOVG7zijRzBCnLw== 289 + 290 + "@esbuild/linux-s390x@0.18.14": 291 + version "0.18.14" 292 + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.14.tgz#3987e30f807b8faf20815b2b2f0a4919084d4e7c" 293 + integrity sha512-G/Lf9iu8sRMM60OVGOh94ZW2nIStksEcITkXdkD09/T6QFD/o+g0+9WVyR/jajIb3A0LvBJ670tBnGe1GgXMgw== 294 + 295 + "@esbuild/linux-x64@0.18.14": 296 + version "0.18.14" 297 + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.18.14.tgz#51c727dc7045c47ab8c08fe6c09cda3e170d99f3" 298 + integrity sha512-TBgStYBQaa3EGhgqIDM+ECnkreb0wkcKqL7H6m+XPcGUoU4dO7dqewfbm0mWEQYH3kzFHrzjOFNpSAVzDZRSJw== 299 + 300 + "@esbuild/netbsd-x64@0.18.14": 301 + version "0.18.14" 302 + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.14.tgz#4677bf88b489d5ffe1b5f0abbb85812996455479" 303 + integrity sha512-stvCcjyCQR2lMTroqNhAbvROqRjxPEq0oQ380YdXxA81TaRJEucH/PzJ/qsEtsHgXlWFW6Ryr/X15vxQiyRXVg== 304 + 305 + "@esbuild/openbsd-x64@0.18.14": 306 + version "0.18.14" 307 + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.14.tgz#939902897e533162450f69266f32ef1325ba98fd" 308 + integrity sha512-apAOJF14CIsN5ht1PA57PboEMsNV70j3FUdxLmA2liZ20gEQnfTG5QU0FhENo5nwbTqCB2O3WDsXAihfODjHYw== 309 + 310 + "@esbuild/sunos-x64@0.18.14": 311 + version "0.18.14" 312 + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.14.tgz#a50721d47b93586249bd63250bd4b7496fc9957b" 313 + integrity sha512-fYRaaS8mDgZcGybPn2MQbn1ZNZx+UXFSUoS5Hd2oEnlsyUcr/l3c6RnXf1bLDRKKdLRSabTmyCy7VLQ7VhGdOQ== 314 + 315 + "@esbuild/win32-arm64@0.18.14": 316 + version "0.18.14" 317 + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.14.tgz#d8531d370e6fd0e0e40f40e016f996bbe4fd5ebf" 318 + integrity sha512-1c44RcxKEJPrVj62XdmYhxXaU/V7auELCmnD+Ri+UCt+AGxTvzxl9uauQhrFso8gj6ZV1DaORV0sT9XSHOAk8Q== 319 + 320 + "@esbuild/win32-ia32@0.18.14": 321 + version "0.18.14" 322 + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.14.tgz#dcbf75e4e65d2921cd4be14ed67cec7360440e46" 323 + integrity sha512-EXAFttrdAxZkFQmpvcAQ2bywlWUsONp/9c2lcfvPUhu8vXBBenCXpoq9YkUvVP639ld3YGiYx0YUQ6/VQz3Maw== 324 + 325 + "@esbuild/win32-x64@0.18.14": 326 + version "0.18.14" 327 + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.14.tgz#43f66032e0f189b6f394d4dbc903a188bb0c969f" 328 + integrity sha512-K0QjGbcskx+gY+qp3v4/940qg8JitpXbdxFhRDA1aYoNaPff88+aEwoq45aqJ+ogpxQxmU0ZTjgnrQD/w8iiUg== 329 + 330 + "@eslint-community/eslint-utils@^4.2.0": 331 + version "4.4.0" 332 + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" 333 + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== 334 + dependencies: 335 + eslint-visitor-keys "^3.3.0" 336 + 337 + "@eslint-community/regexpp@^4.4.0": 338 + version "4.5.1" 339 + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884" 340 + integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ== 341 + 342 + "@eslint/eslintrc@^2.1.0": 343 + version "2.1.0" 344 + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d" 345 + integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A== 346 + dependencies: 347 + ajv "^6.12.4" 348 + debug "^4.3.2" 349 + espree "^9.6.0" 350 + globals "^13.19.0" 351 + ignore "^5.2.0" 352 + import-fresh "^3.2.1" 353 + js-yaml "^4.1.0" 354 + minimatch "^3.1.2" 355 + strip-json-comments "^3.1.1" 356 + 357 + "@eslint/js@8.44.0": 358 + version "8.44.0" 359 + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af" 360 + integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw== 361 + 362 + "@headlessui/react@^1.7.15": 363 + version "1.7.15" 364 + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.15.tgz#53ef6ae132af81b8f188414767b6e79ebf8dc73f" 365 + integrity sha512-OTO0XtoRQ6JPB1cKNFYBZv2Q0JMqMGNhYP1CjPvcJvjz8YGokz8oAj89HIYZGN0gZzn/4kk9iUpmMF4Q21Gsqw== 366 + dependencies: 367 + client-only "^0.0.1" 368 + 369 + "@heroicons/react@^2.0.18": 370 + version "2.0.18" 371 + resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.0.18.tgz#f80301907c243df03c7e9fd76c0286e95361f7c1" 372 + integrity sha512-7TyMjRrZZMBPa+/5Y8lN0iyvUU/01PeMGX2+RE7cQWpEUIcb4QotzUObFkJDejj/HUH4qjP/eQ0gzzKs2f+6Yw== 373 + 374 + "@humanwhocodes/config-array@^0.11.10": 375 + version "0.11.10" 376 + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2" 377 + integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ== 378 + dependencies: 379 + "@humanwhocodes/object-schema" "^1.2.1" 380 + debug "^4.1.1" 381 + minimatch "^3.0.5" 382 + 383 + "@humanwhocodes/module-importer@^1.0.1": 384 + version "1.0.1" 385 + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" 386 + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== 387 + 388 + "@humanwhocodes/object-schema@^1.2.1": 389 + version "1.2.1" 390 + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" 391 + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== 392 + 393 + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": 394 + version "0.3.3" 395 + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" 396 + integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== 397 + dependencies: 398 + "@jridgewell/set-array" "^1.0.1" 399 + "@jridgewell/sourcemap-codec" "^1.4.10" 400 + "@jridgewell/trace-mapping" "^0.3.9" 401 + 402 + "@jridgewell/resolve-uri@3.1.0": 403 + version "3.1.0" 404 + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" 405 + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== 406 + 407 + "@jridgewell/set-array@^1.0.1": 408 + version "1.1.2" 409 + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" 410 + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== 411 + 412 + "@jridgewell/sourcemap-codec@1.4.14": 413 + version "1.4.14" 414 + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" 415 + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== 416 + 417 + "@jridgewell/sourcemap-codec@^1.4.10": 418 + version "1.4.15" 419 + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" 420 + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== 421 + 422 + "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": 423 + version "0.3.18" 424 + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" 425 + integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA== 426 + dependencies: 427 + "@jridgewell/resolve-uri" "3.1.0" 428 + "@jridgewell/sourcemap-codec" "1.4.14" 429 + 430 + "@nodelib/fs.scandir@2.1.5": 431 + version "2.1.5" 432 + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" 433 + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== 434 + dependencies: 435 + "@nodelib/fs.stat" "2.0.5" 436 + run-parallel "^1.1.9" 437 + 438 + "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": 439 + version "2.0.5" 440 + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 441 + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 442 + 443 + "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": 444 + version "1.2.8" 445 + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 446 + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== 447 + dependencies: 448 + "@nodelib/fs.scandir" "2.1.5" 449 + fastq "^1.6.0" 450 + 451 + "@remix-run/router@1.7.2": 452 + version "1.7.2" 453 + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" 454 + integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== 455 + 456 + "@tailwindcss/forms@^0.5.4": 457 + version "0.5.4" 458 + resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.4.tgz#5316a782fd95369eb5b6fd01d46323b3dce656a2" 459 + integrity sha512-YAm12D3R7/9Mh4jFbYSMnsd6jG++8KxogWgqs7hbdo/86aWjjlIEvL7+QYdVELmAI0InXTpZqFIg5e7aDVWI2Q== 460 + dependencies: 461 + mini-svg-data-uri "^1.2.3" 462 + 463 + "@types/json-schema@^7.0.9": 464 + version "7.0.12" 465 + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" 466 + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== 467 + 468 + "@types/prop-types@*": 469 + version "15.7.5" 470 + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" 471 + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== 472 + 473 + "@types/react-dom@^18.2.6": 474 + version "18.2.7" 475 + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" 476 + integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== 477 + dependencies: 478 + "@types/react" "*" 479 + 480 + "@types/react@*", "@types/react@^18.2.14": 481 + version "18.2.15" 482 + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.15.tgz#14792b35df676c20ec3cf595b262f8c615a73066" 483 + integrity sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA== 484 + dependencies: 485 + "@types/prop-types" "*" 486 + "@types/scheduler" "*" 487 + csstype "^3.0.2" 488 + 489 + "@types/scheduler@*": 490 + version "0.16.3" 491 + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" 492 + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== 493 + 494 + "@types/semver@^7.3.12": 495 + version "7.5.0" 496 + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" 497 + integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== 498 + 499 + "@typescript-eslint/eslint-plugin@^5.61.0": 500 + version "5.62.0" 501 + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" 502 + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== 503 + dependencies: 504 + "@eslint-community/regexpp" "^4.4.0" 505 + "@typescript-eslint/scope-manager" "5.62.0" 506 + "@typescript-eslint/type-utils" "5.62.0" 507 + "@typescript-eslint/utils" "5.62.0" 508 + debug "^4.3.4" 509 + graphemer "^1.4.0" 510 + ignore "^5.2.0" 511 + natural-compare-lite "^1.4.0" 512 + semver "^7.3.7" 513 + tsutils "^3.21.0" 514 + 515 + "@typescript-eslint/parser@^5.61.0": 516 + version "5.62.0" 517 + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" 518 + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== 519 + dependencies: 520 + "@typescript-eslint/scope-manager" "5.62.0" 521 + "@typescript-eslint/types" "5.62.0" 522 + "@typescript-eslint/typescript-estree" "5.62.0" 523 + debug "^4.3.4" 524 + 525 + "@typescript-eslint/scope-manager@5.62.0": 526 + version "5.62.0" 527 + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" 528 + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== 529 + dependencies: 530 + "@typescript-eslint/types" "5.62.0" 531 + "@typescript-eslint/visitor-keys" "5.62.0" 532 + 533 + "@typescript-eslint/type-utils@5.62.0": 534 + version "5.62.0" 535 + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" 536 + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== 537 + dependencies: 538 + "@typescript-eslint/typescript-estree" "5.62.0" 539 + "@typescript-eslint/utils" "5.62.0" 540 + debug "^4.3.4" 541 + tsutils "^3.21.0" 542 + 543 + "@typescript-eslint/types@5.62.0": 544 + version "5.62.0" 545 + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" 546 + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== 547 + 548 + "@typescript-eslint/typescript-estree@5.62.0": 549 + version "5.62.0" 550 + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" 551 + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== 552 + dependencies: 553 + "@typescript-eslint/types" "5.62.0" 554 + "@typescript-eslint/visitor-keys" "5.62.0" 555 + debug "^4.3.4" 556 + globby "^11.1.0" 557 + is-glob "^4.0.3" 558 + semver "^7.3.7" 559 + tsutils "^3.21.0" 560 + 561 + "@typescript-eslint/utils@5.62.0": 562 + version "5.62.0" 563 + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" 564 + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== 565 + dependencies: 566 + "@eslint-community/eslint-utils" "^4.2.0" 567 + "@types/json-schema" "^7.0.9" 568 + "@types/semver" "^7.3.12" 569 + "@typescript-eslint/scope-manager" "5.62.0" 570 + "@typescript-eslint/types" "5.62.0" 571 + "@typescript-eslint/typescript-estree" "5.62.0" 572 + eslint-scope "^5.1.1" 573 + semver "^7.3.7" 574 + 575 + "@typescript-eslint/visitor-keys@5.62.0": 576 + version "5.62.0" 577 + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" 578 + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== 579 + dependencies: 580 + "@typescript-eslint/types" "5.62.0" 581 + eslint-visitor-keys "^3.3.0" 582 + 583 + "@vitejs/plugin-react@^4.0.1": 584 + version "4.0.3" 585 + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-4.0.3.tgz#007d27ad5ef1eac4bf8c29e168ba9be2203c371b" 586 + integrity sha512-pwXDog5nwwvSIzwrvYYmA2Ljcd/ZNlcsSG2Q9CNDBwnsd55UGAyr2doXtB5j+2uymRCnCfExlznzzSFbBRcoCg== 587 + dependencies: 588 + "@babel/core" "^7.22.5" 589 + "@babel/plugin-transform-react-jsx-self" "^7.22.5" 590 + "@babel/plugin-transform-react-jsx-source" "^7.22.5" 591 + react-refresh "^0.14.0" 592 + 593 + acorn-jsx@^5.3.2: 594 + version "5.3.2" 595 + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" 596 + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== 597 + 598 + acorn@^8.9.0: 599 + version "8.10.0" 600 + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" 601 + integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== 602 + 603 + ajv@^6.10.0, ajv@^6.12.4: 604 + version "6.12.6" 605 + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" 606 + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 607 + dependencies: 608 + fast-deep-equal "^3.1.1" 609 + fast-json-stable-stringify "^2.0.0" 610 + json-schema-traverse "^0.4.1" 611 + uri-js "^4.2.2" 612 + 613 + ansi-regex@^5.0.1: 614 + version "5.0.1" 615 + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 616 + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 617 + 618 + ansi-styles@^3.2.1: 619 + version "3.2.1" 620 + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 621 + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 622 + dependencies: 623 + color-convert "^1.9.0" 624 + 625 + ansi-styles@^4.1.0: 626 + version "4.3.0" 627 + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 628 + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 629 + dependencies: 630 + color-convert "^2.0.1" 631 + 632 + any-promise@^1.0.0: 633 + version "1.3.0" 634 + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" 635 + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== 636 + 637 + anymatch@~3.1.2: 638 + version "3.1.3" 639 + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" 640 + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== 641 + dependencies: 642 + normalize-path "^3.0.0" 643 + picomatch "^2.0.4" 644 + 645 + arg@^5.0.2: 646 + version "5.0.2" 647 + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" 648 + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== 649 + 650 + argparse@^2.0.1: 651 + version "2.0.1" 652 + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" 653 + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 654 + 655 + array-union@^2.1.0: 656 + version "2.1.0" 657 + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 658 + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 659 + 660 + autoprefixer@^10.4.14: 661 + version "10.4.14" 662 + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.14.tgz#e28d49902f8e759dd25b153264e862df2705f79d" 663 + integrity sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ== 664 + dependencies: 665 + browserslist "^4.21.5" 666 + caniuse-lite "^1.0.30001464" 667 + fraction.js "^4.2.0" 668 + normalize-range "^0.1.2" 669 + picocolors "^1.0.0" 670 + postcss-value-parser "^4.2.0" 671 + 672 + balanced-match@^1.0.0: 673 + version "1.0.2" 674 + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 675 + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 676 + 677 + binary-extensions@^2.0.0: 678 + version "2.2.0" 679 + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 680 + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 681 + 682 + brace-expansion@^1.1.7: 683 + version "1.1.11" 684 + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 685 + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 686 + dependencies: 687 + balanced-match "^1.0.0" 688 + concat-map "0.0.1" 689 + 690 + braces@^3.0.2, braces@~3.0.2: 691 + version "3.0.2" 692 + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 693 + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 694 + dependencies: 695 + fill-range "^7.0.1" 696 + 697 + browserslist@^4.21.5, browserslist@^4.21.9: 698 + version "4.21.9" 699 + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" 700 + integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== 701 + dependencies: 702 + caniuse-lite "^1.0.30001503" 703 + electron-to-chromium "^1.4.431" 704 + node-releases "^2.0.12" 705 + update-browserslist-db "^1.0.11" 706 + 707 + callsites@^3.0.0: 708 + version "3.1.0" 709 + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 710 + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 711 + 712 + camelcase-css@^2.0.1: 713 + version "2.0.1" 714 + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" 715 + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== 716 + 717 + caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: 718 + version "1.0.30001516" 719 + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz#621b1be7d85a8843ee7d210fd9d87b52e3daab3a" 720 + integrity sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g== 721 + 722 + chalk@^2.0.0: 723 + version "2.4.2" 724 + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 725 + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 726 + dependencies: 727 + ansi-styles "^3.2.1" 728 + escape-string-regexp "^1.0.5" 729 + supports-color "^5.3.0" 730 + 731 + chalk@^4.0.0: 732 + version "4.1.2" 733 + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 734 + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 735 + dependencies: 736 + ansi-styles "^4.1.0" 737 + supports-color "^7.1.0" 738 + 739 + chokidar@^3.5.3: 740 + version "3.5.3" 741 + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 742 + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 743 + dependencies: 744 + anymatch "~3.1.2" 745 + braces "~3.0.2" 746 + glob-parent "~5.1.2" 747 + is-binary-path "~2.1.0" 748 + is-glob "~4.0.1" 749 + normalize-path "~3.0.0" 750 + readdirp "~3.6.0" 751 + optionalDependencies: 752 + fsevents "~2.3.2" 753 + 754 + client-only@^0.0.1: 755 + version "0.0.1" 756 + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" 757 + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== 758 + 759 + color-convert@^1.9.0: 760 + version "1.9.3" 761 + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 762 + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 763 + dependencies: 764 + color-name "1.1.3" 765 + 766 + color-convert@^2.0.1: 767 + version "2.0.1" 768 + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 769 + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 770 + dependencies: 771 + color-name "~1.1.4" 772 + 773 + color-name@1.1.3: 774 + version "1.1.3" 775 + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 776 + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== 777 + 778 + color-name@~1.1.4: 779 + version "1.1.4" 780 + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 781 + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 782 + 783 + commander@^4.0.0: 784 + version "4.1.1" 785 + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" 786 + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== 787 + 788 + concat-map@0.0.1: 789 + version "0.0.1" 790 + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 791 + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 792 + 793 + convert-source-map@^1.7.0: 794 + version "1.9.0" 795 + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" 796 + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== 797 + 798 + cross-spawn@^7.0.2: 799 + version "7.0.3" 800 + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" 801 + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== 802 + dependencies: 803 + path-key "^3.1.0" 804 + shebang-command "^2.0.0" 805 + which "^2.0.1" 806 + 807 + cssesc@^3.0.0: 808 + version "3.0.0" 809 + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" 810 + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== 811 + 812 + csstype@^3.0.2: 813 + version "3.1.2" 814 + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" 815 + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== 816 + 817 + debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: 818 + version "4.3.4" 819 + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 820 + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 821 + dependencies: 822 + ms "2.1.2" 823 + 824 + deep-is@^0.1.3: 825 + version "0.1.4" 826 + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" 827 + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== 828 + 829 + didyoumean@^1.2.2: 830 + version "1.2.2" 831 + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" 832 + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== 833 + 834 + dir-glob@^3.0.1: 835 + version "3.0.1" 836 + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 837 + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 838 + dependencies: 839 + path-type "^4.0.0" 840 + 841 + dlv@^1.1.3: 842 + version "1.1.3" 843 + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" 844 + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== 845 + 846 + doctrine@^3.0.0: 847 + version "3.0.0" 848 + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" 849 + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== 850 + dependencies: 851 + esutils "^2.0.2" 852 + 853 + electron-to-chromium@^1.4.431: 854 + version "1.4.463" 855 + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.463.tgz#8eb04355f24fef5c8097661d14e143f6d8554055" 856 + integrity sha512-fT3hvdUWLjDbaTGzyOjng/CQhQJSQP8ThO3XZAoaxHvHo2kUXiRQVMj9M235l8uDFiNPsPa6KHT1p3RaR6ugRw== 857 + 858 + esbuild@^0.18.10: 859 + version "0.18.14" 860 + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.14.tgz#3df4cfef66c55176583359d79fd416ffeb3cdf7e" 861 + integrity sha512-uNPj5oHPYmj+ZhSQeYQVFZ+hAlJZbAGOmmILWIqrGvPVlNLbyOvU5Bu6Woi8G8nskcx0vwY0iFoMPrzT86Ko+w== 862 + optionalDependencies: 863 + "@esbuild/android-arm" "0.18.14" 864 + "@esbuild/android-arm64" "0.18.14" 865 + "@esbuild/android-x64" "0.18.14" 866 + "@esbuild/darwin-arm64" "0.18.14" 867 + "@esbuild/darwin-x64" "0.18.14" 868 + "@esbuild/freebsd-arm64" "0.18.14" 869 + "@esbuild/freebsd-x64" "0.18.14" 870 + "@esbuild/linux-arm" "0.18.14" 871 + "@esbuild/linux-arm64" "0.18.14" 872 + "@esbuild/linux-ia32" "0.18.14" 873 + "@esbuild/linux-loong64" "0.18.14" 874 + "@esbuild/linux-mips64el" "0.18.14" 875 + "@esbuild/linux-ppc64" "0.18.14" 876 + "@esbuild/linux-riscv64" "0.18.14" 877 + "@esbuild/linux-s390x" "0.18.14" 878 + "@esbuild/linux-x64" "0.18.14" 879 + "@esbuild/netbsd-x64" "0.18.14" 880 + "@esbuild/openbsd-x64" "0.18.14" 881 + "@esbuild/sunos-x64" "0.18.14" 882 + "@esbuild/win32-arm64" "0.18.14" 883 + "@esbuild/win32-ia32" "0.18.14" 884 + "@esbuild/win32-x64" "0.18.14" 885 + 886 + escalade@^3.1.1: 887 + version "3.1.1" 888 + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 889 + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 890 + 891 + escape-string-regexp@^1.0.5: 892 + version "1.0.5" 893 + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 894 + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== 895 + 896 + escape-string-regexp@^4.0.0: 897 + version "4.0.0" 898 + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" 899 + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== 900 + 901 + eslint-plugin-react-hooks@^4.6.0: 902 + version "4.6.0" 903 + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" 904 + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== 905 + 906 + eslint-plugin-react-refresh@^0.4.1: 907 + version "0.4.3" 908 + resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.3.tgz#59dae8c00a119f06ea16b1d3e6891df3775947c7" 909 + integrity sha512-Hh0wv8bUNY877+sI0BlCUlsS0TYYQqvzEwJsJJPM2WF4RnTStSnSR3zdJYa2nPOJgg3UghXi54lVyMSmpCalzA== 910 + 911 + eslint-scope@^5.1.1: 912 + version "5.1.1" 913 + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" 914 + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 915 + dependencies: 916 + esrecurse "^4.3.0" 917 + estraverse "^4.1.1" 918 + 919 + eslint-scope@^7.2.0: 920 + version "7.2.1" 921 + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.1.tgz#936821d3462675f25a18ac5fd88a67cc15b393bd" 922 + integrity sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA== 923 + dependencies: 924 + esrecurse "^4.3.0" 925 + estraverse "^5.2.0" 926 + 927 + eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: 928 + version "3.4.1" 929 + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" 930 + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== 931 + 932 + eslint@^8.44.0: 933 + version "8.45.0" 934 + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.45.0.tgz#bab660f90d18e1364352c0a6b7c6db8edb458b78" 935 + integrity sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw== 936 + dependencies: 937 + "@eslint-community/eslint-utils" "^4.2.0" 938 + "@eslint-community/regexpp" "^4.4.0" 939 + "@eslint/eslintrc" "^2.1.0" 940 + "@eslint/js" "8.44.0" 941 + "@humanwhocodes/config-array" "^0.11.10" 942 + "@humanwhocodes/module-importer" "^1.0.1" 943 + "@nodelib/fs.walk" "^1.2.8" 944 + ajv "^6.10.0" 945 + chalk "^4.0.0" 946 + cross-spawn "^7.0.2" 947 + debug "^4.3.2" 948 + doctrine "^3.0.0" 949 + escape-string-regexp "^4.0.0" 950 + eslint-scope "^7.2.0" 951 + eslint-visitor-keys "^3.4.1" 952 + espree "^9.6.0" 953 + esquery "^1.4.2" 954 + esutils "^2.0.2" 955 + fast-deep-equal "^3.1.3" 956 + file-entry-cache "^6.0.1" 957 + find-up "^5.0.0" 958 + glob-parent "^6.0.2" 959 + globals "^13.19.0" 960 + graphemer "^1.4.0" 961 + ignore "^5.2.0" 962 + imurmurhash "^0.1.4" 963 + is-glob "^4.0.0" 964 + is-path-inside "^3.0.3" 965 + js-yaml "^4.1.0" 966 + json-stable-stringify-without-jsonify "^1.0.1" 967 + levn "^0.4.1" 968 + lodash.merge "^4.6.2" 969 + minimatch "^3.1.2" 970 + natural-compare "^1.4.0" 971 + optionator "^0.9.3" 972 + strip-ansi "^6.0.1" 973 + text-table "^0.2.0" 974 + 975 + espree@^9.6.0: 976 + version "9.6.1" 977 + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" 978 + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== 979 + dependencies: 980 + acorn "^8.9.0" 981 + acorn-jsx "^5.3.2" 982 + eslint-visitor-keys "^3.4.1" 983 + 984 + esquery@^1.4.2: 985 + version "1.5.0" 986 + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" 987 + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== 988 + dependencies: 989 + estraverse "^5.1.0" 990 + 991 + esrecurse@^4.3.0: 992 + version "4.3.0" 993 + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" 994 + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 995 + dependencies: 996 + estraverse "^5.2.0" 997 + 998 + estraverse@^4.1.1: 999 + version "4.3.0" 1000 + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" 1001 + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 1002 + 1003 + estraverse@^5.1.0, estraverse@^5.2.0: 1004 + version "5.3.0" 1005 + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" 1006 + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== 1007 + 1008 + esutils@^2.0.2: 1009 + version "2.0.3" 1010 + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" 1011 + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== 1012 + 1013 + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: 1014 + version "3.1.3" 1015 + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 1016 + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== 1017 + 1018 + fast-glob@^3.2.12, fast-glob@^3.2.9: 1019 + version "3.3.0" 1020 + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" 1021 + integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA== 1022 + dependencies: 1023 + "@nodelib/fs.stat" "^2.0.2" 1024 + "@nodelib/fs.walk" "^1.2.3" 1025 + glob-parent "^5.1.2" 1026 + merge2 "^1.3.0" 1027 + micromatch "^4.0.4" 1028 + 1029 + fast-json-stable-stringify@^2.0.0: 1030 + version "2.1.0" 1031 + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 1032 + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== 1033 + 1034 + fast-levenshtein@^2.0.6: 1035 + version "2.0.6" 1036 + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" 1037 + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== 1038 + 1039 + fastq@^1.6.0: 1040 + version "1.15.0" 1041 + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" 1042 + integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== 1043 + dependencies: 1044 + reusify "^1.0.4" 1045 + 1046 + file-entry-cache@^6.0.1: 1047 + version "6.0.1" 1048 + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" 1049 + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== 1050 + dependencies: 1051 + flat-cache "^3.0.4" 1052 + 1053 + fill-range@^7.0.1: 1054 + version "7.0.1" 1055 + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 1056 + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 1057 + dependencies: 1058 + to-regex-range "^5.0.1" 1059 + 1060 + find-up@^5.0.0: 1061 + version "5.0.0" 1062 + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" 1063 + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== 1064 + dependencies: 1065 + locate-path "^6.0.0" 1066 + path-exists "^4.0.0" 1067 + 1068 + flat-cache@^3.0.4: 1069 + version "3.0.4" 1070 + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" 1071 + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== 1072 + dependencies: 1073 + flatted "^3.1.0" 1074 + rimraf "^3.0.2" 1075 + 1076 + flatted@^3.1.0: 1077 + version "3.2.7" 1078 + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" 1079 + integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== 1080 + 1081 + fraction.js@^4.2.0: 1082 + version "4.2.0" 1083 + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" 1084 + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== 1085 + 1086 + fs.realpath@^1.0.0: 1087 + version "1.0.0" 1088 + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 1089 + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 1090 + 1091 + fsevents@~2.3.2: 1092 + version "2.3.2" 1093 + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 1094 + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 1095 + 1096 + function-bind@^1.1.1: 1097 + version "1.1.1" 1098 + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 1099 + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 1100 + 1101 + gensync@^1.0.0-beta.2: 1102 + version "1.0.0-beta.2" 1103 + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 1104 + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== 1105 + 1106 + glob-parent@^5.1.2, glob-parent@~5.1.2: 1107 + version "5.1.2" 1108 + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 1109 + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 1110 + dependencies: 1111 + is-glob "^4.0.1" 1112 + 1113 + glob-parent@^6.0.2: 1114 + version "6.0.2" 1115 + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" 1116 + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== 1117 + dependencies: 1118 + is-glob "^4.0.3" 1119 + 1120 + glob@7.1.6: 1121 + version "7.1.6" 1122 + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" 1123 + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== 1124 + dependencies: 1125 + fs.realpath "^1.0.0" 1126 + inflight "^1.0.4" 1127 + inherits "2" 1128 + minimatch "^3.0.4" 1129 + once "^1.3.0" 1130 + path-is-absolute "^1.0.0" 1131 + 1132 + glob@^7.1.3: 1133 + version "7.2.3" 1134 + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" 1135 + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== 1136 + dependencies: 1137 + fs.realpath "^1.0.0" 1138 + inflight "^1.0.4" 1139 + inherits "2" 1140 + minimatch "^3.1.1" 1141 + once "^1.3.0" 1142 + path-is-absolute "^1.0.0" 1143 + 1144 + globals@^11.1.0: 1145 + version "11.12.0" 1146 + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 1147 + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 1148 + 1149 + globals@^13.19.0: 1150 + version "13.20.0" 1151 + resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82" 1152 + integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ== 1153 + dependencies: 1154 + type-fest "^0.20.2" 1155 + 1156 + globby@^11.1.0: 1157 + version "11.1.0" 1158 + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" 1159 + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== 1160 + dependencies: 1161 + array-union "^2.1.0" 1162 + dir-glob "^3.0.1" 1163 + fast-glob "^3.2.9" 1164 + ignore "^5.2.0" 1165 + merge2 "^1.4.1" 1166 + slash "^3.0.0" 1167 + 1168 + graphemer@^1.4.0: 1169 + version "1.4.0" 1170 + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" 1171 + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== 1172 + 1173 + has-flag@^3.0.0: 1174 + version "3.0.0" 1175 + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 1176 + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 1177 + 1178 + has-flag@^4.0.0: 1179 + version "4.0.0" 1180 + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 1181 + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 1182 + 1183 + has@^1.0.3: 1184 + version "1.0.3" 1185 + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 1186 + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 1187 + dependencies: 1188 + function-bind "^1.1.1" 1189 + 1190 + ignore@^5.2.0: 1191 + version "5.2.4" 1192 + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324" 1193 + integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== 1194 + 1195 + import-fresh@^3.2.1: 1196 + version "3.3.0" 1197 + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" 1198 + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 1199 + dependencies: 1200 + parent-module "^1.0.0" 1201 + resolve-from "^4.0.0" 1202 + 1203 + imurmurhash@^0.1.4: 1204 + version "0.1.4" 1205 + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 1206 + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== 1207 + 1208 + inflight@^1.0.4: 1209 + version "1.0.6" 1210 + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 1211 + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== 1212 + dependencies: 1213 + once "^1.3.0" 1214 + wrappy "1" 1215 + 1216 + inherits@2: 1217 + version "2.0.4" 1218 + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 1219 + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 1220 + 1221 + is-binary-path@~2.1.0: 1222 + version "2.1.0" 1223 + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 1224 + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 1225 + dependencies: 1226 + binary-extensions "^2.0.0" 1227 + 1228 + is-core-module@^2.11.0: 1229 + version "2.12.1" 1230 + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" 1231 + integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== 1232 + dependencies: 1233 + has "^1.0.3" 1234 + 1235 + is-extglob@^2.1.1: 1236 + version "2.1.1" 1237 + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 1238 + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 1239 + 1240 + is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: 1241 + version "4.0.3" 1242 + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 1243 + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 1244 + dependencies: 1245 + is-extglob "^2.1.1" 1246 + 1247 + is-number@^7.0.0: 1248 + version "7.0.0" 1249 + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 1250 + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 1251 + 1252 + is-path-inside@^3.0.3: 1253 + version "3.0.3" 1254 + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" 1255 + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== 1256 + 1257 + isexe@^2.0.0: 1258 + version "2.0.0" 1259 + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 1260 + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== 1261 + 1262 + jiti@^1.18.2: 1263 + version "1.19.1" 1264 + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1" 1265 + integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== 1266 + 1267 + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: 1268 + version "4.0.0" 1269 + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 1270 + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 1271 + 1272 + js-yaml@^4.1.0: 1273 + version "4.1.0" 1274 + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" 1275 + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== 1276 + dependencies: 1277 + argparse "^2.0.1" 1278 + 1279 + jsesc@^2.5.1: 1280 + version "2.5.2" 1281 + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 1282 + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== 1283 + 1284 + json-schema-traverse@^0.4.1: 1285 + version "0.4.1" 1286 + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 1287 + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== 1288 + 1289 + json-stable-stringify-without-jsonify@^1.0.1: 1290 + version "1.0.1" 1291 + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" 1292 + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== 1293 + 1294 + json5@^2.2.2: 1295 + version "2.2.3" 1296 + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" 1297 + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== 1298 + 1299 + levn@^0.4.1: 1300 + version "0.4.1" 1301 + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" 1302 + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== 1303 + dependencies: 1304 + prelude-ls "^1.2.1" 1305 + type-check "~0.4.0" 1306 + 1307 + lilconfig@^2.0.5, lilconfig@^2.1.0: 1308 + version "2.1.0" 1309 + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" 1310 + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== 1311 + 1312 + lines-and-columns@^1.1.6: 1313 + version "1.2.4" 1314 + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" 1315 + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== 1316 + 1317 + locate-path@^6.0.0: 1318 + version "6.0.0" 1319 + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" 1320 + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== 1321 + dependencies: 1322 + p-locate "^5.0.0" 1323 + 1324 + lodash.merge@^4.6.2: 1325 + version "4.6.2" 1326 + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" 1327 + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 1328 + 1329 + loose-envify@^1.1.0: 1330 + version "1.4.0" 1331 + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 1332 + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 1333 + dependencies: 1334 + js-tokens "^3.0.0 || ^4.0.0" 1335 + 1336 + lru-cache@^5.1.1: 1337 + version "5.1.1" 1338 + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" 1339 + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== 1340 + dependencies: 1341 + yallist "^3.0.2" 1342 + 1343 + lru-cache@^6.0.0: 1344 + version "6.0.0" 1345 + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 1346 + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 1347 + dependencies: 1348 + yallist "^4.0.0" 1349 + 1350 + merge2@^1.3.0, merge2@^1.4.1: 1351 + version "1.4.1" 1352 + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 1353 + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 1354 + 1355 + micromatch@^4.0.4, micromatch@^4.0.5: 1356 + version "4.0.5" 1357 + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" 1358 + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== 1359 + dependencies: 1360 + braces "^3.0.2" 1361 + picomatch "^2.3.1" 1362 + 1363 + mini-svg-data-uri@^1.2.3: 1364 + version "1.4.4" 1365 + resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939" 1366 + integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg== 1367 + 1368 + minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: 1369 + version "3.1.2" 1370 + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 1371 + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 1372 + dependencies: 1373 + brace-expansion "^1.1.7" 1374 + 1375 + ms@2.1.2: 1376 + version "2.1.2" 1377 + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 1378 + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 1379 + 1380 + mz@^2.7.0: 1381 + version "2.7.0" 1382 + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" 1383 + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== 1384 + dependencies: 1385 + any-promise "^1.0.0" 1386 + object-assign "^4.0.1" 1387 + thenify-all "^1.0.0" 1388 + 1389 + nanoid@^3.3.6: 1390 + version "3.3.6" 1391 + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" 1392 + integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== 1393 + 1394 + natural-compare-lite@^1.4.0: 1395 + version "1.4.0" 1396 + resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" 1397 + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== 1398 + 1399 + natural-compare@^1.4.0: 1400 + version "1.4.0" 1401 + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 1402 + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== 1403 + 1404 + node-releases@^2.0.12: 1405 + version "2.0.13" 1406 + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" 1407 + integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== 1408 + 1409 + normalize-path@^3.0.0, normalize-path@~3.0.0: 1410 + version "3.0.0" 1411 + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 1412 + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 1413 + 1414 + normalize-range@^0.1.2: 1415 + version "0.1.2" 1416 + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" 1417 + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== 1418 + 1419 + object-assign@^4.0.1: 1420 + version "4.1.1" 1421 + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 1422 + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== 1423 + 1424 + object-hash@^3.0.0: 1425 + version "3.0.0" 1426 + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" 1427 + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== 1428 + 1429 + once@^1.3.0: 1430 + version "1.4.0" 1431 + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 1432 + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 1433 + dependencies: 1434 + wrappy "1" 1435 + 1436 + optionator@^0.9.3: 1437 + version "0.9.3" 1438 + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" 1439 + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== 1440 + dependencies: 1441 + "@aashutoshrathi/word-wrap" "^1.2.3" 1442 + deep-is "^0.1.3" 1443 + fast-levenshtein "^2.0.6" 1444 + levn "^0.4.1" 1445 + prelude-ls "^1.2.1" 1446 + type-check "^0.4.0" 1447 + 1448 + p-limit@^3.0.2: 1449 + version "3.1.0" 1450 + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" 1451 + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== 1452 + dependencies: 1453 + yocto-queue "^0.1.0" 1454 + 1455 + p-locate@^5.0.0: 1456 + version "5.0.0" 1457 + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" 1458 + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== 1459 + dependencies: 1460 + p-limit "^3.0.2" 1461 + 1462 + parent-module@^1.0.0: 1463 + version "1.0.1" 1464 + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 1465 + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 1466 + dependencies: 1467 + callsites "^3.0.0" 1468 + 1469 + path-exists@^4.0.0: 1470 + version "4.0.0" 1471 + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" 1472 + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== 1473 + 1474 + path-is-absolute@^1.0.0: 1475 + version "1.0.1" 1476 + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1477 + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== 1478 + 1479 + path-key@^3.1.0: 1480 + version "3.1.1" 1481 + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 1482 + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 1483 + 1484 + path-parse@^1.0.7: 1485 + version "1.0.7" 1486 + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 1487 + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 1488 + 1489 + path-type@^4.0.0: 1490 + version "4.0.0" 1491 + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 1492 + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 1493 + 1494 + picocolors@^1.0.0: 1495 + version "1.0.0" 1496 + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 1497 + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 1498 + 1499 + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: 1500 + version "2.3.1" 1501 + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 1502 + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 1503 + 1504 + pify@^2.3.0: 1505 + version "2.3.0" 1506 + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 1507 + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== 1508 + 1509 + pirates@^4.0.1: 1510 + version "4.0.6" 1511 + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" 1512 + integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== 1513 + 1514 + postcss-import@^15.1.0: 1515 + version "15.1.0" 1516 + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" 1517 + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== 1518 + dependencies: 1519 + postcss-value-parser "^4.0.0" 1520 + read-cache "^1.0.0" 1521 + resolve "^1.1.7" 1522 + 1523 + postcss-js@^4.0.1: 1524 + version "4.0.1" 1525 + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" 1526 + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== 1527 + dependencies: 1528 + camelcase-css "^2.0.1" 1529 + 1530 + postcss-load-config@^4.0.1: 1531 + version "4.0.1" 1532 + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd" 1533 + integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== 1534 + dependencies: 1535 + lilconfig "^2.0.5" 1536 + yaml "^2.1.1" 1537 + 1538 + postcss-nested@^6.0.1: 1539 + version "6.0.1" 1540 + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" 1541 + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== 1542 + dependencies: 1543 + postcss-selector-parser "^6.0.11" 1544 + 1545 + postcss-selector-parser@^6.0.11: 1546 + version "6.0.13" 1547 + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" 1548 + integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== 1549 + dependencies: 1550 + cssesc "^3.0.0" 1551 + util-deprecate "^1.0.2" 1552 + 1553 + postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: 1554 + version "4.2.0" 1555 + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" 1556 + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== 1557 + 1558 + postcss@^8.4.23, postcss@^8.4.25, postcss@^8.4.26: 1559 + version "8.4.26" 1560 + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94" 1561 + integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw== 1562 + dependencies: 1563 + nanoid "^3.3.6" 1564 + picocolors "^1.0.0" 1565 + source-map-js "^1.0.2" 1566 + 1567 + prelude-ls@^1.2.1: 1568 + version "1.2.1" 1569 + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" 1570 + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== 1571 + 1572 + punycode@^2.1.0: 1573 + version "2.3.0" 1574 + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" 1575 + integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== 1576 + 1577 + queue-microtask@^1.2.2: 1578 + version "1.2.3" 1579 + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 1580 + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 1581 + 1582 + react-dom@^18.2.0: 1583 + version "18.2.0" 1584 + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" 1585 + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== 1586 + dependencies: 1587 + loose-envify "^1.1.0" 1588 + scheduler "^0.23.0" 1589 + 1590 + react-refresh@^0.14.0: 1591 + version "0.14.0" 1592 + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e" 1593 + integrity sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ== 1594 + 1595 + react-router-dom@^6.14.2: 1596 + version "6.14.2" 1597 + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.14.2.tgz#88f520118b91aa60233bd08dbd3fdcaea3a68488" 1598 + integrity sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg== 1599 + dependencies: 1600 + "@remix-run/router" "1.7.2" 1601 + react-router "6.14.2" 1602 + 1603 + react-router@6.14.2: 1604 + version "6.14.2" 1605 + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.14.2.tgz#1f60994d8c369de7b8ba7a78d8f7ec23df76b300" 1606 + integrity sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ== 1607 + dependencies: 1608 + "@remix-run/router" "1.7.2" 1609 + 1610 + react@^18.2.0: 1611 + version "18.2.0" 1612 + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" 1613 + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== 1614 + dependencies: 1615 + loose-envify "^1.1.0" 1616 + 1617 + read-cache@^1.0.0: 1618 + version "1.0.0" 1619 + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" 1620 + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== 1621 + dependencies: 1622 + pify "^2.3.0" 1623 + 1624 + readdirp@~3.6.0: 1625 + version "3.6.0" 1626 + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 1627 + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 1628 + dependencies: 1629 + picomatch "^2.2.1" 1630 + 1631 + resolve-from@^4.0.0: 1632 + version "4.0.0" 1633 + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 1634 + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 1635 + 1636 + resolve@^1.1.7, resolve@^1.22.2: 1637 + version "1.22.2" 1638 + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" 1639 + integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g== 1640 + dependencies: 1641 + is-core-module "^2.11.0" 1642 + path-parse "^1.0.7" 1643 + supports-preserve-symlinks-flag "^1.0.0" 1644 + 1645 + reusify@^1.0.4: 1646 + version "1.0.4" 1647 + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 1648 + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 1649 + 1650 + rimraf@^3.0.2: 1651 + version "3.0.2" 1652 + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" 1653 + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== 1654 + dependencies: 1655 + glob "^7.1.3" 1656 + 1657 + rollup@^3.25.2: 1658 + version "3.26.3" 1659 + resolved "https://registry.yarnpkg.com/rollup/-/rollup-3.26.3.tgz#bbc8818cadd0aebca348dbb3d68d296d220967b8" 1660 + integrity sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ== 1661 + optionalDependencies: 1662 + fsevents "~2.3.2" 1663 + 1664 + run-parallel@^1.1.9: 1665 + version "1.2.0" 1666 + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" 1667 + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 1668 + dependencies: 1669 + queue-microtask "^1.2.2" 1670 + 1671 + scheduler@^0.23.0: 1672 + version "0.23.0" 1673 + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" 1674 + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== 1675 + dependencies: 1676 + loose-envify "^1.1.0" 1677 + 1678 + semver@^6.3.1: 1679 + version "6.3.1" 1680 + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" 1681 + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== 1682 + 1683 + semver@^7.3.7: 1684 + version "7.5.4" 1685 + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" 1686 + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== 1687 + dependencies: 1688 + lru-cache "^6.0.0" 1689 + 1690 + shebang-command@^2.0.0: 1691 + version "2.0.0" 1692 + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 1693 + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 1694 + dependencies: 1695 + shebang-regex "^3.0.0" 1696 + 1697 + shebang-regex@^3.0.0: 1698 + version "3.0.0" 1699 + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 1700 + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 1701 + 1702 + slash@^3.0.0: 1703 + version "3.0.0" 1704 + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 1705 + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 1706 + 1707 + source-map-js@^1.0.2: 1708 + version "1.0.2" 1709 + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 1710 + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 1711 + 1712 + strip-ansi@^6.0.1: 1713 + version "6.0.1" 1714 + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 1715 + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1716 + dependencies: 1717 + ansi-regex "^5.0.1" 1718 + 1719 + strip-json-comments@^3.1.1: 1720 + version "3.1.1" 1721 + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 1722 + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1723 + 1724 + sucrase@^3.32.0: 1725 + version "3.33.0" 1726 + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.33.0.tgz#092c8d2f99a191f2cd9f1fdd52113772f4241f6e" 1727 + integrity sha512-ARGC7vbufOHfpvyGcZZXFaXCMZ9A4fffOGC5ucOW7+WHDGlAe8LJdf3Jts1sWhDeiI1RSWrKy5Hodl+JWGdW2A== 1728 + dependencies: 1729 + "@jridgewell/gen-mapping" "^0.3.2" 1730 + commander "^4.0.0" 1731 + glob "7.1.6" 1732 + lines-and-columns "^1.1.6" 1733 + mz "^2.7.0" 1734 + pirates "^4.0.1" 1735 + ts-interface-checker "^0.1.9" 1736 + 1737 + supports-color@^5.3.0: 1738 + version "5.5.0" 1739 + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1740 + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1741 + dependencies: 1742 + has-flag "^3.0.0" 1743 + 1744 + supports-color@^7.1.0: 1745 + version "7.2.0" 1746 + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1747 + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1748 + dependencies: 1749 + has-flag "^4.0.0" 1750 + 1751 + supports-preserve-symlinks-flag@^1.0.0: 1752 + version "1.0.0" 1753 + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 1754 + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 1755 + 1756 + tailwindcss@^3.3.3: 1757 + version "3.3.3" 1758 + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf" 1759 + integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== 1760 + dependencies: 1761 + "@alloc/quick-lru" "^5.2.0" 1762 + arg "^5.0.2" 1763 + chokidar "^3.5.3" 1764 + didyoumean "^1.2.2" 1765 + dlv "^1.1.3" 1766 + fast-glob "^3.2.12" 1767 + glob-parent "^6.0.2" 1768 + is-glob "^4.0.3" 1769 + jiti "^1.18.2" 1770 + lilconfig "^2.1.0" 1771 + micromatch "^4.0.5" 1772 + normalize-path "^3.0.0" 1773 + object-hash "^3.0.0" 1774 + picocolors "^1.0.0" 1775 + postcss "^8.4.23" 1776 + postcss-import "^15.1.0" 1777 + postcss-js "^4.0.1" 1778 + postcss-load-config "^4.0.1" 1779 + postcss-nested "^6.0.1" 1780 + postcss-selector-parser "^6.0.11" 1781 + resolve "^1.22.2" 1782 + sucrase "^3.32.0" 1783 + 1784 + text-table@^0.2.0: 1785 + version "0.2.0" 1786 + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1787 + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== 1788 + 1789 + thenify-all@^1.0.0: 1790 + version "1.6.0" 1791 + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" 1792 + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== 1793 + dependencies: 1794 + thenify ">= 3.1.0 < 4" 1795 + 1796 + "thenify@>= 3.1.0 < 4": 1797 + version "3.3.1" 1798 + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" 1799 + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== 1800 + dependencies: 1801 + any-promise "^1.0.0" 1802 + 1803 + to-fast-properties@^2.0.0: 1804 + version "2.0.0" 1805 + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 1806 + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== 1807 + 1808 + to-regex-range@^5.0.1: 1809 + version "5.0.1" 1810 + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 1811 + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 1812 + dependencies: 1813 + is-number "^7.0.0" 1814 + 1815 + ts-interface-checker@^0.1.9: 1816 + version "0.1.13" 1817 + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" 1818 + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== 1819 + 1820 + tslib@^1.8.1: 1821 + version "1.14.1" 1822 + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 1823 + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 1824 + 1825 + tsutils@^3.21.0: 1826 + version "3.21.0" 1827 + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" 1828 + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== 1829 + dependencies: 1830 + tslib "^1.8.1" 1831 + 1832 + type-check@^0.4.0, type-check@~0.4.0: 1833 + version "0.4.0" 1834 + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" 1835 + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== 1836 + dependencies: 1837 + prelude-ls "^1.2.1" 1838 + 1839 + type-fest@^0.20.2: 1840 + version "0.20.2" 1841 + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" 1842 + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== 1843 + 1844 + typescript@^5.0.2: 1845 + version "5.1.6" 1846 + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" 1847 + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== 1848 + 1849 + update-browserslist-db@^1.0.11: 1850 + version "1.0.11" 1851 + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" 1852 + integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== 1853 + dependencies: 1854 + escalade "^3.1.1" 1855 + picocolors "^1.0.0" 1856 + 1857 + uri-js@^4.2.2: 1858 + version "4.4.1" 1859 + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" 1860 + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== 1861 + dependencies: 1862 + punycode "^2.1.0" 1863 + 1864 + util-deprecate@^1.0.2: 1865 + version "1.0.2" 1866 + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1867 + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 1868 + 1869 + vite@^4.4.0: 1870 + version "4.4.4" 1871 + resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.4.tgz#b76e6049c0e080cb54e735ad2d18287753752118" 1872 + integrity sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg== 1873 + dependencies: 1874 + esbuild "^0.18.10" 1875 + postcss "^8.4.25" 1876 + rollup "^3.25.2" 1877 + optionalDependencies: 1878 + fsevents "~2.3.2" 1879 + 1880 + which@^2.0.1: 1881 + version "2.0.2" 1882 + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 1883 + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 1884 + dependencies: 1885 + isexe "^2.0.0" 1886 + 1887 + wrappy@1: 1888 + version "1.0.2" 1889 + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1890 + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 1891 + 1892 + yallist@^3.0.2: 1893 + version "3.1.1" 1894 + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" 1895 + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== 1896 + 1897 + yallist@^4.0.0: 1898 + version "4.0.0" 1899 + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1900 + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1901 + 1902 + yaml@^2.1.1: 1903 + version "2.3.1" 1904 + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" 1905 + integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== 1906 + 1907 + yocto-queue@^0.1.0: 1908 + version "0.1.0" 1909 + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 1910 + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==