├── .dockerignore ├── internal ├── embedded │ ├── webdist │ │ └── keep │ └── embed.go ├── singleton │ └── singleton.go ├── util │ ├── slice.go │ └── fiber.go ├── config │ ├── config.go │ └── paerser.go ├── database │ ├── errors.go │ ├── database.go │ └── nuts │ │ └── nuts.go └── web │ ├── middleware │ └── auth.go │ ├── controllers │ ├── auth.go │ └── devices.go │ └── web.go ├── web ├── .env.development ├── src │ ├── components │ │ ├── init │ │ │ ├── index.ts │ │ │ └── Init.tsx │ │ ├── login │ │ │ ├── index.ts │ │ │ └── Login.tsx │ │ ├── header │ │ │ ├── index.ts │ │ │ └── Header.tsx │ │ ├── skeleton │ │ │ ├── index.ts │ │ │ └── Skeleton.tsx │ │ ├── snackbar │ │ │ ├── index.ts │ │ │ └── Snackbar.tsx │ │ ├── pingstate │ │ │ ├── index.ts │ │ │ └── Pingstate.tsx │ │ ├── device │ │ │ ├── index.ts │ │ │ ├── Container.tsx │ │ │ ├── NewDevice.tsx │ │ │ └── Device.tsx │ │ ├── Label.tsx │ │ ├── Input.tsx │ │ ├── Select.tsx │ │ └── Button.tsx │ ├── vite-env.d.ts │ ├── styled.d.ts │ ├── services │ │ ├── api.ts │ │ └── store.ts │ ├── favicon.svg │ ├── assets │ │ ├── add-white.svg │ │ ├── back.svg │ │ ├── mobile.svg │ │ ├── logout.svg │ │ ├── pc.svg │ │ ├── refresh.svg │ │ ├── server.svg │ │ └── bulb.svg │ ├── main.tsx │ ├── index.css │ ├── api │ │ ├── errors.ts │ │ ├── models.ts │ │ └── api.ts │ ├── theme │ │ └── theme.ts │ ├── hooks │ │ ├── usePing.ts │ │ ├── useSnackbar.ts │ │ ├── useDevices.ts │ │ ├── useAuth.ts │ │ └── useDevice.ts │ ├── routes │ │ ├── Login.tsx │ │ ├── Main.tsx │ │ └── Device.tsx │ └── App.tsx ├── tsconfig.node.json ├── vite.config.ts ├── .gitignore ├── index.html ├── tsconfig.json ├── package.json └── yarn.lock ├── pkg ├── auth │ ├── errors.go │ ├── auth.go │ └── jwt.go ├── hasher │ ├── hasher.go │ └── argon2id.go ├── opt │ └── opt.go ├── imcp │ └── imcp.go ├── models │ ├── config.go │ ├── api.go │ └── device.go └── wol │ └── wol.go ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── go.mod ├── cmd └── webwol │ └── main.go ├── .github └── workflows │ └── build.yml └── go.sum /.dockerignore: -------------------------------------------------------------------------------- 1 | web/node_modules -------------------------------------------------------------------------------- /internal/embedded/webdist/keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/.env.development: -------------------------------------------------------------------------------- 1 | VITE_APIENDPOINT="http://localhost:8080" -------------------------------------------------------------------------------- /web/src/components/init/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Init"; 2 | -------------------------------------------------------------------------------- /web/src/components/login/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Login"; 2 | -------------------------------------------------------------------------------- /web/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /web/src/components/header/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Header"; 2 | -------------------------------------------------------------------------------- /web/src/components/skeleton/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Skeleton"; 2 | -------------------------------------------------------------------------------- /web/src/components/snackbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Snackbar"; 2 | -------------------------------------------------------------------------------- /web/src/components/pingstate/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Pingstate"; 2 | -------------------------------------------------------------------------------- /web/src/components/device/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Device"; 2 | export * from "./NewDevice"; 3 | -------------------------------------------------------------------------------- /internal/singleton/singleton.go: -------------------------------------------------------------------------------- 1 | package singleton 2 | 3 | var ( 4 | InitVerificationKey string 5 | ) 6 | -------------------------------------------------------------------------------- /internal/embedded/embed.go: -------------------------------------------------------------------------------- 1 | package embedded 2 | 3 | import "embed" 4 | 5 | var ( 6 | //go:embed webdist 7 | FrontendFiles embed.FS 8 | ) 9 | -------------------------------------------------------------------------------- /internal/util/slice.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | func Unnilify[T any](s []T) []T { 4 | if s == nil { 5 | return make([]T, 0) 6 | } 7 | return s 8 | } 9 | -------------------------------------------------------------------------------- /pkg/auth/errors.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrExpired = errors.New("expired") 7 | ErrEmptyIdent = errors.New("empty ident") 8 | ) 9 | -------------------------------------------------------------------------------- /pkg/hasher/hasher.go: -------------------------------------------------------------------------------- 1 | package hasher 2 | 3 | type Hasher interface { 4 | Generate(v string) (hsh string, err error) 5 | Equal(v string, hsh string) (match bool, err error) 6 | } 7 | -------------------------------------------------------------------------------- /pkg/opt/opt.go: -------------------------------------------------------------------------------- 1 | package opt 2 | 3 | func Opt[T any](v []T, def ...T) T { 4 | if len(v) != 0 { 5 | return v[0] 6 | } 7 | var vDef T 8 | return Opt(def, vDef) 9 | } 10 | -------------------------------------------------------------------------------- /web/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/zekrotja/webwol/pkg/models" 4 | 5 | type Config interface { 6 | Load() (err error) 7 | Instance() (cfg *models.Config) 8 | } 9 | -------------------------------------------------------------------------------- /web/src/styled.d.ts: -------------------------------------------------------------------------------- 1 | import "styled-components"; 2 | import { Theme } from "./theme/theme"; 3 | 4 | declare module "styled-components" { 5 | export interface DefaultTheme extends Theme {} 6 | } 7 | -------------------------------------------------------------------------------- /web/src/services/api.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "../api/api"; 2 | 3 | const APIENDPOINT = (import.meta.env.VITE_APIENDPOINT ?? "") + "/api"; 4 | 5 | export const ClientInstance = new Client(APIENDPOINT); 6 | -------------------------------------------------------------------------------- /pkg/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import "time" 4 | 5 | type Auth interface { 6 | GetToken(ident string) (token string, exp time.Time, err error) 7 | Validate(token string) (ident string, err error) 8 | } 9 | -------------------------------------------------------------------------------- /internal/database/errors.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrNotFound = errors.New("not found") 7 | ) 8 | 9 | func IsErrDatabaseNotFound(err error) bool { 10 | return err == ErrNotFound 11 | } 12 | -------------------------------------------------------------------------------- /web/src/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/add-white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | import svgr from "vite-plugin-svgr"; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(), svgr()], 8 | }); 9 | -------------------------------------------------------------------------------- /web/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './index.css' 4 | import App from './App' 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ) 12 | -------------------------------------------------------------------------------- /web/src/assets/mobile.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/assets/logout.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/src/components/Label.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Label = styled.label` 4 | display: block; 5 | margin-bottom: 0.8em; 6 | font-size: 0.75em; 7 | font-weight: bold; 8 | text-transform: uppercase; 9 | opacity: 0.6; 10 | `; 11 | 12 | export default Label; 13 | -------------------------------------------------------------------------------- /web/src/assets/pc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vsc/* 2 | private.* 3 | bin/* 4 | *.exe 5 | *.db 6 | vendor/ 7 | web/node_modules/ 8 | web/dist/ 9 | __debug_bin 10 | deploy/ 11 | ./data/ 12 | .vscode/.ropeproject 13 | cmd/test/* 14 | .DS_Store 15 | 16 | data/ 17 | .env 18 | .vscode 19 | 20 | internal/embedded/webdist/* 21 | !internal/embedded/webdist/keep -------------------------------------------------------------------------------- /web/src/assets/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /internal/util/fiber.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | ) 8 | 9 | func QueryInt(ctx *fiber.Ctx, key string, def int) (i int, err error) { 10 | v := ctx.Query(key) 11 | if v == "" { 12 | i = def 13 | return 14 | } 15 | i, err = strconv.Atoi(v) 16 | return 17 | } 18 | -------------------------------------------------------------------------------- /web/src/assets/server.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /web/src/assets/bulb.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | WebWOL 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /web/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /web/src/api/errors.ts: -------------------------------------------------------------------------------- 1 | import { ErrorReponse } from "./models"; 2 | 3 | export class APIError extends Error { 4 | constructor(private _res: Response, private _body?: ErrorReponse) { 5 | super(_body?.error ?? "unknown"); 6 | } 7 | 8 | get response() { 9 | return this._res; 10 | } 11 | 12 | get status() { 13 | return this._res.status; 14 | } 15 | 16 | get code() { 17 | return this._body?.code ?? 0; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /internal/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import "github.com/zekrotja/webwol/pkg/models" 4 | 5 | type Database interface { 6 | SetRootPWHash(hsh []byte) (err error) 7 | GetRootPWHash() (hsh []byte, err error) 8 | 9 | ListDevices() (ds []*models.Device, err error) 10 | GetDevice(id string) (d *models.Device, err error) 11 | SetDevice(d *models.Device) (err error) 12 | RemoveDevice(id string) (err error) 13 | 14 | Close() (err error) 15 | } 16 | -------------------------------------------------------------------------------- /pkg/imcp/imcp.go: -------------------------------------------------------------------------------- 1 | package imcp 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-ping/ping" 7 | ) 8 | 9 | func Ping(ipAddr string, retries int) (stats *ping.Statistics, err error) { 10 | pinger, err := ping.NewPinger(ipAddr) 11 | if err != nil { 12 | return 13 | } 14 | 15 | pinger.Count = retries 16 | pinger.SetPrivileged(true) 17 | pinger.Timeout = 5 * time.Second 18 | err = pinger.Run() 19 | stats = pinger.Statistics() 20 | 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /pkg/models/config.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | var DefaultConfig = Config{ 4 | DbLocation: "data/db", 5 | BindAddress: "0.0.0.0:80", 6 | JWTLifetimeSeconds: 30 * 60, 7 | } 8 | 9 | type Config struct { 10 | DbLocation string `json:"dblocation"` 11 | BindAddress string `json:"bindaddress"` 12 | CorsOrigins string `json:"corsorigins"` 13 | JWTSigningKey string `json:"jwtsigningkey"` 14 | JWTLifetimeSeconds int `json:"jwtlifetimeseconds"` 15 | } 16 | -------------------------------------------------------------------------------- /web/src/components/Input.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Input = styled.input` 4 | width: 100%; 5 | background-color: rgba(0, 0, 0, 0.2); 6 | border-radius: 10px; 7 | padding: 0.5em 0.8em; 8 | font-size: 1em; 9 | color: ${(p) => p.theme.text}; 10 | border: none; 11 | outline: solid 2px transparent; 12 | transition: outline 0.2s ease; 13 | 14 | &:focus { 15 | outline: solid 2px ${(p) => p.theme.accent}; 16 | } 17 | `; 18 | 19 | export default Input; 20 | -------------------------------------------------------------------------------- /web/src/components/Select.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Select = styled.select` 4 | width: 100%; 5 | background-color: rgba(0, 0, 0, 0.2); 6 | border-radius: 10px; 7 | padding: 0.5em 0.8em; 8 | font-size: 1em; 9 | color: ${(p) => p.theme.text}; 10 | border: none; 11 | outline: solid 2px transparent; 12 | transition: outline 0.2s ease; 13 | 14 | &:focus { 15 | outline: solid 2px ${(p) => p.theme.accent}; 16 | } 17 | `; 18 | 19 | export default Select; 20 | -------------------------------------------------------------------------------- /web/src/components/device/Container.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | export const Container = styled.div` 4 | color: ${(p) => p.theme.text}; 5 | width: 100%; 6 | border-radius: 10px; 7 | margin-bottom: 1em; 8 | height: 5em; 9 | cursor: pointer; 10 | transition: transform 0.2s ease; 11 | 12 | &:hover { 13 | transform: scale(1.02); 14 | } 15 | 16 | @media screen and (min-width: 52em) { 17 | max-width: 25em; 18 | margin-left: 0.5em; 19 | margin-right: 0.5em; 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /web/src/components/device/NewDevice.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Container } from "./Container"; 3 | 4 | import AddIcon from "../../assets/add-white.svg"; 5 | 6 | export const NewDevice = styled(Container)` 7 | border: dashed 2px ${(p) => p.theme.text}; 8 | opacity: 0.5; 9 | color: white; 10 | background-image: url("${AddIcon}"); 11 | background-repeat: no-repeat; 12 | background-position: center; 13 | transition: all 0.2s ease; 14 | 15 | &:hover { 16 | opacity: 0.9; 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine AS buildfe 2 | WORKDIR /build 3 | COPY web . 4 | RUN yarn 5 | RUN yarn run build 6 | 7 | FROM golang:1.18rc1-alpine AS buildbe 8 | ARG GOOS=linux 9 | ARG GOARCH=amd64 10 | WORKDIR /build 11 | COPY . . 12 | COPY --from=buildfe /build/dist ./internal/embedded/webdist 13 | ENV GOOS=$GOOS GOARCH=$GOARCH 14 | RUN go build -o bin/webwol cmd/webwol/main.go 15 | 16 | FROM alpine:latest 17 | WORKDIR /app 18 | COPY --from=buildbe /build/bin/webwol /app/webwol 19 | VOLUME ["/app/data"] 20 | EXPOSE 80 21 | ENTRYPOINT ["/app/webwol"] -------------------------------------------------------------------------------- /web/src/services/store.ts: -------------------------------------------------------------------------------- 1 | import create from "zustand"; 2 | import { SnackbarState } from "../hooks/useSnackbar"; 3 | 4 | export type State = { 5 | snackBar: SnackbarState; 6 | setSnackBar: (snackBar: SnackbarState) => void; 7 | 8 | loggedIn: boolean; 9 | setLoggedIn: (loggedIn: boolean) => void; 10 | }; 11 | 12 | export const useStore = create((set) => ({ 13 | snackBar: { show: false }, 14 | setSnackBar: (snackBar) => set({ snackBar }), 15 | 16 | loggedIn: false, 17 | setLoggedIn: (loggedIn) => set({ loggedIn }), 18 | })); 19 | -------------------------------------------------------------------------------- /web/src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | const Button = styled.button` 4 | background: ${(p) => p.theme.accentGrad}; 5 | color: ${(p) => p.theme.text}; 6 | border: none; 7 | font-size: 1em; 8 | border-radius: 10px; 9 | padding: 0.5em 0.8em; 10 | cursor: pointer; 11 | text-transform: uppercase; 12 | transition: box-shadow 0.2s ease; 13 | 14 | &:hover { 15 | box-shadow: 0 0.2em 2em 0 black; 16 | } 17 | 18 | &:disabled { 19 | cursor: not-allowed; 20 | opacity: 0.5; 21 | } 22 | `; 23 | 24 | export default Button; 25 | -------------------------------------------------------------------------------- /web/src/theme/theme.ts: -------------------------------------------------------------------------------- 1 | export const DarkTheme = { 2 | background: "#23262d", 3 | backgtroundDark: "#1c1f26", 4 | text: "#f4f4f5", 5 | accent: "#198ce5", 6 | accentGrad: "linear-gradient(125deg, #198ce5 0%, #2a7ab8 100%)", 7 | redGrad: "linear-gradient(125deg, #c12449 0%, #96253f 100%)", 8 | 9 | info: "#2483c1", 10 | warning: "#c17824", 11 | success: "#27a56d", 12 | error: "#c12449", 13 | 14 | gray: "#595959", 15 | online: "#24d87e", 16 | offline: "#d82457", 17 | }; 18 | 19 | export const DefaultTheme = DarkTheme; 20 | export type Theme = typeof DefaultTheme; 21 | -------------------------------------------------------------------------------- /internal/web/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/zekrotja/webwol/pkg/auth" 8 | ) 9 | 10 | func Auth(ath auth.Auth) fiber.Handler { 11 | return func(ctx *fiber.Ctx) (err error) { 12 | token := ctx.Get("authorization") 13 | 14 | if len(token) < 8 || !strings.HasPrefix(strings.ToLower(token), "bearer ") { 15 | return fiber.ErrUnauthorized 16 | } 17 | 18 | _, err = ath.Validate(token[7:]) 19 | 20 | if err != nil { 21 | return fiber.ErrUnauthorized 22 | } 23 | 24 | return ctx.Next() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/wol/wol.go: -------------------------------------------------------------------------------- 1 | package wol 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/sabhiram/go-wol/wol" 7 | ) 8 | 9 | func SendMagicPacket(macAddr, bcastAddr string) (err error) { 10 | packet, err := wol.New(macAddr) 11 | if err != nil { 12 | return 13 | } 14 | payload, err := packet.Marshal() 15 | if err != nil { 16 | return 17 | } 18 | 19 | if bcastAddr == "" { 20 | bcastAddr = "255.255.255.255:9" 21 | } 22 | bcAddr, err := net.ResolveUDPAddr("udp", bcastAddr) 23 | if err != nil { 24 | return 25 | } 26 | 27 | conn, err := net.DialUDP("udp", nil, bcAddr) 28 | if err != nil { 29 | return 30 | } 31 | defer conn.Close() 32 | 33 | _, err = conn.Write(payload) 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "types": ["vite-plugin-svgr/client"] 19 | }, 20 | "include": ["src"], 21 | "references": [{ "path": "./tsconfig.node.json" }] 22 | } 23 | -------------------------------------------------------------------------------- /web/src/hooks/usePing.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { Device } from "../api/models"; 3 | import { ClientInstance } from "../services/api"; 4 | 5 | export default function usePing(device: Device) { 6 | const [state, setState] = useState(null); 7 | 8 | const refresh = (delay = 0) => { 9 | if (device.ip_address) { 10 | setState(null); 11 | setTimeout( 12 | () => 13 | ClientInstance.ping(device.uid).then(({ successful }) => 14 | setState(successful) 15 | ), 16 | delay 17 | ); 18 | } 19 | }; 20 | 21 | useEffect(() => { 22 | refresh(); 23 | }, []); 24 | 25 | return { state, refresh }; 26 | } 27 | -------------------------------------------------------------------------------- /pkg/hasher/argon2id.go: -------------------------------------------------------------------------------- 1 | package hasher 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/alexedwards/argon2id" 7 | ) 8 | 9 | type Argon2ID struct { 10 | params *argon2id.Params 11 | } 12 | 13 | var _ Hasher = (*Argon2ID)(nil) 14 | 15 | func NewArgon2ID() *Argon2ID { 16 | return &Argon2ID{ 17 | params: &argon2id.Params{ 18 | Memory: 128 * 1024, 19 | Iterations: 4, 20 | Parallelism: uint8(runtime.NumCPU()), 21 | SaltLength: 16, 22 | KeyLength: 32, 23 | }, 24 | } 25 | } 26 | 27 | func (h *Argon2ID) Generate(v string) (hsh string, err error) { 28 | return argon2id.CreateHash(v, h.params) 29 | } 30 | 31 | func (h *Argon2ID) Equal(v string, hsh string) (match bool, err error) { 32 | return argon2id.ComparePasswordAndHash(v, hsh) 33 | } 34 | -------------------------------------------------------------------------------- /web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webwol", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@types/events": "^3.0.0", 12 | "@types/styled-components": "^5.1.23", 13 | "events": "^3.3.0", 14 | "react": "^17.0.2", 15 | "react-dom": "^17.0.2", 16 | "react-router": "^6.2.1", 17 | "react-router-dom": "^6.2.1", 18 | "styled-components": "^5.3.3", 19 | "zustand": "^3.7.0" 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^17.0.33", 23 | "@types/react-dom": "^17.0.10", 24 | "@vitejs/plugin-react": "^1.0.7", 25 | "typescript": "^4.5.4", 26 | "vite": "^2.8.0", 27 | "vite-plugin-svgr": "^1.0.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/src/hooks/useSnackbar.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | import { useStore } from "../services/store"; 3 | 4 | export type SnackbarType = "error" | "success" | "info" | "warning"; 5 | 6 | export type SnackbarState = { 7 | show: boolean; 8 | type?: SnackbarType; 9 | payload?: string | JSX.Element; 10 | }; 11 | 12 | export function useSnackbar() { 13 | const setSnackBar = useStore((s) => s.setSnackBar); 14 | 15 | const timerRef = useRef>(); 16 | 17 | const hide = () => setSnackBar({ show: false }); 18 | 19 | const show = ( 20 | type: SnackbarType, 21 | payload: string | JSX.Element, 22 | timeout: number = 5000 23 | ) => { 24 | if (timerRef.current) clearTimeout(timerRef.current); 25 | setSnackBar({ show: true, type, payload }); 26 | timerRef.current = setTimeout(() => hide(), timeout); 27 | }; 28 | 29 | return { show, hide }; 30 | } 31 | -------------------------------------------------------------------------------- /web/src/api/models.ts: -------------------------------------------------------------------------------- 1 | export interface ErrorReponse { 2 | error: string; 3 | code: number; 4 | context: string; 5 | } 6 | 7 | export interface Status { 8 | code: number; 9 | message: string; 10 | } 11 | 12 | export interface LoginRequest { 13 | password: string; 14 | } 15 | 16 | export interface TokenResponse { 17 | token: string; 18 | expires: string; 19 | } 20 | 21 | export interface InitializedResponse { 22 | is_initialized: boolean; 23 | } 24 | 25 | export interface InitializationRequest extends LoginRequest { 26 | key: string; 27 | } 28 | 29 | export interface PingResponse { 30 | successful: boolean; 31 | rtt: number; 32 | } 33 | 34 | export enum DeviceType { 35 | UNSPECIFIED, 36 | PC, 37 | SERVER, 38 | IOT, 39 | MOBILE, 40 | } 41 | 42 | export interface Device { 43 | uid: string; 44 | name: string; 45 | type: DeviceType; 46 | mac_address: string; 47 | ip_address: string; 48 | } 49 | -------------------------------------------------------------------------------- /pkg/models/api.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import "time" 4 | 5 | var ( 6 | StatusOK = &Status{200, "ok"} 7 | ) 8 | 9 | type Error struct { 10 | Error string `json:"error"` 11 | Code int `json:"code"` 12 | Context string `json:"context,omitempty"` 13 | } 14 | 15 | type Status struct { 16 | Code int `json:"code"` 17 | Message string `json:"message"` 18 | } 19 | 20 | type LoginRequest struct { 21 | Password string `json:"password"` 22 | } 23 | 24 | type TokenResponse struct { 25 | Token string `json:"token"` 26 | Expires time.Time `json:"expires"` 27 | } 28 | 29 | type InitializedResponse struct { 30 | IsInitialiezd bool `json:"is_initialized"` 31 | } 32 | 33 | type InitializationRequest struct { 34 | LoginRequest 35 | 36 | Key string `json:"key"` 37 | } 38 | 39 | type PingResponse struct { 40 | Successful bool `json:"successful"` 41 | RTT int64 `json:"rtt"` 42 | SuccessRate float32 `json:"success_rate"` 43 | } 44 | -------------------------------------------------------------------------------- /web/src/components/pingstate/Pingstate.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | interface Props { 4 | online: boolean | null; 5 | } 6 | 7 | const Container = styled.div` 8 | display: flex; 9 | align-items: center; 10 | 11 | > *:first-child { 12 | margin-right: 0.4em; 13 | } 14 | 15 | > span { 16 | padding-bottom: 0.15em; 17 | } 18 | `; 19 | 20 | const Dot = styled.div` 21 | width: 0.9em; 22 | height: 0.9em; 23 | border-radius: 100%; 24 | background-color: ${(p) => { 25 | if (p.online === null) return p.theme.gray; 26 | if (p.online) return p.theme.online; 27 | return p.theme.offline; 28 | }}; 29 | `; 30 | 31 | export const Pingstate: React.FC = ({ online }) => { 32 | const desc = online === null ? "pinging ..." : online ? "online" : "offline"; 33 | 34 | return ( 35 | 36 | 37 | {desc} 38 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /internal/config/paerser.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/joho/godotenv" 7 | "github.com/traefik/paerser/env" 8 | "github.com/traefik/paerser/file" 9 | "github.com/zekrotja/webwol/pkg/models" 10 | ) 11 | 12 | const defaultConfigLoc = "./config.yaml" 13 | 14 | type Paerser struct { 15 | cfg *models.Config 16 | configFile string 17 | } 18 | 19 | func NewPaerser(configFile string) *Paerser { 20 | return &Paerser{ 21 | configFile: configFile, 22 | } 23 | } 24 | 25 | func (p *Paerser) Instance() *models.Config { 26 | return p.cfg 27 | } 28 | 29 | func (p *Paerser) Load() (err error) { 30 | godotenv.Load() 31 | 32 | cfg := models.DefaultConfig 33 | 34 | cfgFile := defaultConfigLoc 35 | if p.configFile != "" { 36 | cfgFile = p.configFile 37 | } 38 | if err = file.Decode(cfgFile, &cfg); err != nil && !os.IsNotExist(err) { 39 | return 40 | } 41 | 42 | if err = env.Decode(os.Environ(), "WW_", &cfg); err != nil { 43 | return 44 | } 45 | 46 | p.cfg = &cfg 47 | 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /web/src/hooks/useDevices.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { APIError } from "../api/errors"; 3 | import { Device } from "../api/models"; 4 | import { ClientInstance } from "../services/api"; 5 | import useAuth from "./useAuth"; 6 | import { useSnackbar } from "./useSnackbar"; 7 | 8 | export default function useDevices() { 9 | const [devices, setDevices] = useState([]); 10 | const { logout } = useAuth(true); 11 | const { show } = useSnackbar(); 12 | 13 | const wakeUp = (dev: Device) => ClientInstance.wake(dev.uid); 14 | 15 | const refresh = () => { 16 | setDevices([]); 17 | return ClientInstance.list() 18 | .then((dev) => setDevices(dev)) 19 | .catch((err) => { 20 | if (err instanceof APIError) { 21 | if (err.code === 401) { 22 | logout(); 23 | } else { 24 | show("error", `${err.code}: ${err.message}`); 25 | } 26 | } 27 | }); 28 | }; 29 | 30 | useEffect(() => { 31 | refresh(); 32 | }, []); 33 | 34 | return { devices, wakeUp, refresh }; 35 | } 36 | -------------------------------------------------------------------------------- /web/src/components/skeleton/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | interface Props { 4 | width?: string; 5 | height?: string; 6 | delay?: string | number; 7 | margin?: string; 8 | } 9 | 10 | const SkeletonContainer = styled.div` 11 | width: ${(p) => p.width}; 12 | height: ${(p) => p.height}; 13 | background-color: #ffffff3d; 14 | border-radius: 10px; 15 | opacity: 0; 16 | 17 | @keyframes skeleton { 18 | 0% { 19 | opacity: 0; 20 | } 21 | 50% { 22 | opacity: 1; 23 | } 24 | 100% { 25 | opacity: 0; 26 | } 27 | } 28 | 29 | animation: skeleton 1.5s ease infinite; 30 | animation-delay: ${(p) => p.delay}; 31 | `; 32 | 33 | export const SkeletonTile: React.FC = ({ delay, ...props }) => { 34 | return ; 35 | }; 36 | 37 | SkeletonTile.defaultProps = { 38 | width: "100%", 39 | height: "3em", 40 | delay: 0, 41 | margin: "0", 42 | }; 43 | 44 | function asTime(v: string | number): string { 45 | if (typeof v === "number") return `${v}s`; 46 | return v; 47 | } 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Ringo Hoffmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webwol 2 | 3 | An absolutely overengineered web application for waking up network devices via wake on lan. 4 | 5 | ## Demo 6 | 7 | https://user-images.githubusercontent.com/16734205/156153234-49bfdef8-510f-455a-aa7a-84e73bb359a2.mp4 8 | 9 | ## Setup 10 | 11 | You might want to deploy the application to an always running server in your local network like a Raspberry Pi or NAS. The recommended way is to use the provided [`docker-compose.yml`](docker-compose.yml). It contains the service configuration for the webwol app as well as for traefik as reverse proxy. If you use the provided Docker Compose config, you must enter your e-mail address as well as your DynDNS domain in the service configurations. 12 | 13 | To access your server from the internet, you need to specify a port forwarding configuration in your home router for the device, both for port `80` (for the HTTP ACME challenge) as well as `443`. 14 | 15 | The webwol service is a single binary containing the web files and runs mostly with zero configuration required. After first startup, you must initialize the service using the initialization key printed into the log and a provided password you want to use to log in later. -------------------------------------------------------------------------------- /web/src/components/login/Login.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import styled from "styled-components"; 3 | import Button from "../Button"; 4 | import Input from "../Input"; 5 | import Label from "../Label"; 6 | 7 | interface Props { 8 | onSubmit: (password: string) => void; 9 | } 10 | 11 | const Section = styled.section` 12 | margin-top: 1em; 13 | `; 14 | 15 | const StyledButton = styled(Button)` 16 | width: 100%; 17 | margin-top: 1em; 18 | `; 19 | 20 | export const Login: React.FC = ({ onSubmit }) => { 21 | const [password, setPassword] = useState(""); 22 | 23 | const valid = !!password; 24 | 25 | return ( 26 | <> 27 |

Login

28 |
29 | 30 | setPassword(e.currentTarget.value)} 35 | onKeyPress={(e) => e.code === "Enter" && onSubmit(password)} 36 | /> 37 |
38 |
39 | onSubmit(password)}> 40 | Login 41 | 42 |
43 | 44 | ); 45 | }; 46 | -------------------------------------------------------------------------------- /web/src/components/snackbar/Snackbar.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { SnackbarType } from "../../hooks/useSnackbar"; 3 | import { useStore } from "../../services/store"; 4 | 5 | const Container = styled.div<{ type?: SnackbarType; show?: boolean }>` 6 | display: block; 7 | position: fixed; 8 | bottom: 0; 9 | left: 0; 10 | right: 0; 11 | padding: 1em; 12 | color: ${(p) => p.theme.text}; 13 | pointer-events: none; 14 | transition: all 0.3s ease-in-out; 15 | 16 | background-color: ${(p) => { 17 | switch (p.type) { 18 | case "error": 19 | return p.theme.error; 20 | case "success": 21 | return p.theme.success; 22 | case "warning": 23 | return p.theme.warning; 24 | default: 25 | return p.theme.info; 26 | } 27 | }}; 28 | 29 | opacity: ${(p) => (p.show ? 1 : 0)}; 30 | transform: translateY(${(p) => (p.show ? 0 : 3)}em); 31 | `; 32 | 33 | export const Snackbar: React.FC = () => { 34 | const state = useStore((s) => s.snackBar); 35 | 36 | return {wrapPayload(state.payload!)}; 37 | }; 38 | 39 | function wrapPayload(pl: string | JSX.Element): JSX.Element { 40 | return typeof pl === "string" ? <>{pl} : pl; 41 | } 42 | -------------------------------------------------------------------------------- /web/src/routes/Login.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Init } from "../components/init"; 3 | import { Login } from "../components/login"; 4 | import { SkeletonTile } from "../components/skeleton"; 5 | import useAuth from "../hooks/useAuth"; 6 | 7 | interface Props {} 8 | 9 | const Container = styled.div` 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | height: 100%; 14 | width: 100%; 15 | 16 | > section { 17 | width: 100%; 18 | max-width: 20em; 19 | } 20 | `; 21 | 22 | const FormContainer = styled.div` 23 | width: 100%; 24 | height: fit-content; 25 | border-radius: 10px; 26 | background-color: ${(p) => p.theme.backgtroundDark}; 27 | padding: 1em; 28 | `; 29 | 30 | export const LoginRoute: React.FC = ({}) => { 31 | const { isInitialized, init, login } = useAuth(); 32 | 33 | return ( 34 | 35 |
36 | {(isInitialized === null && ) || ( 37 | 38 | {(isInitialized && ) || ( 39 | 40 | )} 41 | 42 | )} 43 |
44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /pkg/models/device.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net" 7 | "regexp" 8 | ) 9 | 10 | var ( 11 | reMacAddress = regexp.MustCompile(`^(?:[0-9a-fA-F]{2}[:-]){5}(?:[0-9a-fA-F]{2})$`) 12 | ) 13 | 14 | type DeviceType int 15 | 16 | const ( 17 | DeviceTypeUnspecified DeviceType = iota 18 | DeviceTypePC 19 | DeviceTypeServer 20 | DeviceTypeIOT 21 | DeviceTypeMobile 22 | 23 | maxType 24 | ) 25 | 26 | type Device struct { 27 | UID string `json:"uid"` 28 | Name string `json:"name"` 29 | Type DeviceType `json:"type"` 30 | MacAddress string `json:"mac_address"` 31 | IPAddress string `json:"ip_address,omitempty"` 32 | } 33 | 34 | func UnmarshalDevice(v []byte) (dev Device, err error) { 35 | err = json.Unmarshal(v, &dev) 36 | return 37 | } 38 | 39 | func (dev Device) Marshal() (v []byte, err error) { 40 | return json.Marshal(dev) 41 | } 42 | 43 | func (dev Device) Validate() (err error) { 44 | if dev.Name == "" { 45 | return errors.New("invalid name") 46 | } 47 | if dev.MacAddress == "" || !reMacAddress.MatchString(dev.MacAddress) { 48 | return errors.New("invalid mac address") 49 | } 50 | if dev.Type < DeviceTypeUnspecified || dev.Type >= maxType { 51 | return errors.New("invalid device type") 52 | } 53 | if dev.IPAddress != "" && net.ParseIP(dev.IPAddress) == nil { 54 | return errors.New("invalid ip address") 55 | } 56 | return 57 | } 58 | -------------------------------------------------------------------------------- /web/src/routes/Main.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from "react-router"; 2 | import styled from "styled-components"; 3 | import { DeviceElement, NewDevice } from "../components/device"; 4 | import useDevices from "../hooks/useDevices"; 5 | import { EventEmitter } from "events"; 6 | import { useEffect } from "react"; 7 | 8 | interface Props { 9 | refreshEmitter: EventEmitter; 10 | } 11 | 12 | const Container = styled.div` 13 | width: 100%; 14 | @media screen and (min-width: 52em) { 15 | display: flex; 16 | flex-wrap: wrap; 17 | justify-content: center; 18 | } 19 | `; 20 | 21 | export const MainRoute: React.FC = ({ refreshEmitter }) => { 22 | const { devices, wakeUp, refresh } = useDevices(); 23 | const nav = useNavigate(); 24 | 25 | const _onEdit = (uid: string) => { 26 | nav("/" + uid); 27 | }; 28 | 29 | useEffect(() => { 30 | console.log("register refresh"); 31 | refreshEmitter.addListener("refresh", refresh); 32 | return () => { 33 | refreshEmitter.removeListener("refresh", refresh); 34 | }; 35 | }, []); 36 | 37 | const deviceElements = devices.map((d) => ( 38 | _onEdit(d.uid)} 42 | onWakeUp={() => wakeUp(d)} 43 | /> 44 | )); 45 | deviceElements.push( 46 | _onEdit("new")} /> 47 | ); 48 | 49 | return {deviceElements}; 50 | }; 51 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | traefik: 5 | image: traefik:latest 6 | command: 7 | - "--log.level=DEBUG" 8 | - "--providers.docker" 9 | - "--providers.file.directory=/etc/traefik/dynamic_conf" 10 | - "--entrypoints.http.address=:80" 11 | - "--entrypoints.https.address=:443" 12 | - "--providers.docker.exposedByDefault=false" 13 | - "--certificatesResolvers.le.acme.email=your@email" # ⚠️ <- enter your email here 14 | - "--certificatesResolvers.le.acme.storage=/etc/certstore/acme.json" 15 | - "--certificatesResolvers.le.acme.httpChallenge.entryPoint=http" 16 | volumes: 17 | - "/var/run/docker.sock:/var/run/docker.sock" 18 | - "./traefik/config:/etc/traefik/dynamic_conf" 19 | - "/etc/cert:/etc/cert:ro" 20 | - "/etc/certstore:/etc/certstore" 21 | - "/etc/localtime:/etc/localtime:ro" 22 | network_mode: "host" 23 | restart: unless-stopped 24 | 25 | webwol: 26 | image: ghcr.io/zekrotja/webwol:arm64 27 | volumes: 28 | - "./webwol:/app/data" 29 | restart: unless-stopped 30 | environment: 31 | WW_BINDADDRESS: "0.0.0.0:9090" 32 | network_mode: "host" 33 | labels: 34 | traefik.enable: "true" 35 | traefik.http.routers.webwol.entrypoints: "https" 36 | traefik.http.routers.webwol.tls: "true" 37 | traefik.http.routers.webwol.tls.certresolver: "le" 38 | traefik.http.routers.webwol.rule: "Host(`some.dyndns.host`) && PathPrefix(`/`)" # ⚠️ <- enter your DynDNS Domain here 39 | traefik.http.services.webwol.loadbalancer.server.port: "9090" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zekrotja/webwol 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/robbert229/jwt v2.0.0+incompatible 7 | github.com/sabhiram/go-wol v0.0.0-20211224004021-c83b0c2f887d 8 | github.com/sirupsen/logrus v1.8.1 9 | github.com/zekroTJA/shinpuru v0.0.0-20220223004228-ea95cbaf5fbd 10 | ) 11 | 12 | require ( 13 | github.com/BurntSushi/toml v1.0.0 // indirect 14 | github.com/andybalholm/brotli v1.0.4 // indirect 15 | github.com/google/uuid v1.3.0 // indirect 16 | github.com/klauspost/compress v1.14.1 // indirect 17 | github.com/kr/pretty v0.2.1 // indirect 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/valyala/bytebufferpool v1.0.0 // indirect 20 | github.com/valyala/fasthttp v1.33.0 // indirect 21 | github.com/valyala/tcplisten v1.0.0 // indirect 22 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect 23 | golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d // indirect 24 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect 25 | gopkg.in/yaml.v2 v2.4.0 // indirect 26 | ) 27 | 28 | require ( 29 | github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 30 | github.com/bwmarrin/snowflake v0.3.0 // indirect 31 | github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 32 | github.com/gofiber/fiber/v2 v2.27.0 33 | github.com/joho/godotenv v1.4.0 34 | github.com/rs/xid v1.3.0 35 | github.com/traefik/paerser v0.1.4 36 | github.com/xujiajun/mmap-go v1.0.1 // indirect 37 | github.com/xujiajun/nutsdb v0.6.0 38 | github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b // indirect 39 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /web/src/components/header/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation, useNavigate, useResolvedPath } from "react-router"; 2 | import styled from "styled-components"; 3 | import useAuth from "../../hooks/useAuth"; 4 | import { useStore } from "../../services/store"; 5 | 6 | import { ReactComponent as BackIcon } from "../../assets/back.svg"; 7 | import { ReactComponent as LogoutIcon } from "../../assets/logout.svg"; 8 | import { ReactComponent as RefreshIcon } from "../../assets/refresh.svg"; 9 | 10 | interface Props { 11 | onRefresh?: () => void; 12 | } 13 | 14 | const StyledHeader = styled.header` 15 | padding: 0.5em 1em; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | background: ${(p) => p.theme.accentGrad}; 20 | box-shadow: 0 -1.2em 3em 0 ${(p) => p.theme.accent}; 21 | `; 22 | 23 | const Section = styled.section` 24 | svg { 25 | height: 2.5em; 26 | cursor: pointer; 27 | margin-right: 1em; 28 | &:last-child { 29 | margin-right: 0; 30 | } 31 | } 32 | `; 33 | 34 | export const Header: React.FC = ({ onRefresh }) => { 35 | const loggedIn = useStore((s) => s.loggedIn); 36 | const { logout } = useAuth(false); 37 | const nav = useNavigate(); 38 | const loc = useLocation(); 39 | 40 | const _refresh = () => { 41 | if (!onRefresh) return; 42 | onRefresh(); 43 | }; 44 | 45 | const _isHome = loc.pathname === "/"; 46 | 47 | return loggedIn ? ( 48 | 49 |
{_isHome || nav(-1)} />}
50 |
51 | {!!onRefresh && } 52 | 53 |
54 |
55 | ) : ( 56 | <> 57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /web/src/hooks/useAuth.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useNavigate } from "react-router"; 3 | import { APIError } from "../api/errors"; 4 | import { ClientInstance } from "../services/api"; 5 | import { useStore } from "../services/store"; 6 | import { useSnackbar } from "./useSnackbar"; 7 | 8 | export default function useAuth(skipInitCheck = false) { 9 | const [isInitialized, setIsInitialized] = useState(null); 10 | const setLoggedIn = useStore((s) => s.setLoggedIn); 11 | const { show } = useSnackbar(); 12 | const nav = useNavigate(); 13 | 14 | const init = (key: string, password: string) => 15 | ClientInstance.initialize(key, password) 16 | .then(() => { 17 | setIsInitialized(true); 18 | show( 19 | "success", 20 | "Successfully initialized. You can now log in using the previously given password." 21 | ); 22 | }) 23 | .catch((err) => { 24 | if (err instanceof APIError) { 25 | if (err.code === 403) { 26 | show("error", "Invalid initialization key."); 27 | } else { 28 | show("error", `${err.code}: ${err.message}`); 29 | } 30 | } 31 | }); 32 | 33 | const login = (password: string) => 34 | ClientInstance.login(password) 35 | .then(() => { 36 | setLoggedIn(true); 37 | nav("/"); 38 | }) 39 | .catch((err) => { 40 | if (err instanceof APIError) { 41 | if (err.code === 401) { 42 | show("error", "Invalid credentials."); 43 | } 44 | } 45 | }); 46 | 47 | const logout = () => { 48 | setLoggedIn(false); 49 | ClientInstance.logout(); 50 | nav("/login"); 51 | }; 52 | 53 | if (!skipInitCheck) { 54 | useEffect(() => { 55 | ClientInstance.isInitialized().then(({ is_initialized }) => 56 | setIsInitialized(is_initialized) 57 | ); 58 | }, []); 59 | } 60 | 61 | return { isInitialized, init, login, logout }; 62 | } 63 | -------------------------------------------------------------------------------- /web/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; 2 | import { MainRoute } from "./routes/Main"; 3 | import styled, { createGlobalStyle, ThemeProvider } from "styled-components"; 4 | import { LoginRoute } from "./routes/Login"; 5 | import { DefaultTheme } from "./theme/theme"; 6 | import { Snackbar } from "./components/snackbar"; 7 | import { DeviceRoute } from "./routes/Device"; 8 | import { Header } from "./components/header"; 9 | import { EventEmitter } from "events"; 10 | import { useRef } from "react"; 11 | 12 | const GlobalStyle = createGlobalStyle` 13 | body { 14 | background-color: ${(p) => p.theme.background}; 15 | color: ${(p) => p.theme.text}; 16 | 17 | * { 18 | box-sizing: border-box; 19 | } 20 | 21 | h1 { 22 | margin: 0 0 1em 0; 23 | } 24 | } 25 | `; 26 | 27 | const Container = styled.div` 28 | height: 100vh; 29 | display: flex; 30 | flex-direction: column; 31 | `; 32 | 33 | const RouterOutlet = styled.div` 34 | padding: 2em 1em 0em 1em; 35 | overflow-y: auto; 36 | height: 100%; 37 | `; 38 | 39 | const App: React.FC = () => { 40 | const emitterRef = useRef(new EventEmitter()); 41 | 42 | return ( 43 | <> 44 | 45 | 46 | 47 |
emitterRef.current.emit("refresh")} /> 48 | 49 | 50 | } 53 | /> 54 | } /> 55 | } /> 56 | } /> 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ); 66 | }; 67 | 68 | export default App; 69 | -------------------------------------------------------------------------------- /pkg/auth/jwt.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/robbert229/jwt" 8 | "github.com/zekroTJA/shinpuru/pkg/random" 9 | "github.com/zekrotja/webwol/pkg/opt" 10 | ) 11 | 12 | type JwtAuthConfig struct { 13 | SigningKey string 14 | Lifetime time.Duration 15 | } 16 | 17 | type JwtAuth struct { 18 | mx sync.Mutex 19 | 20 | cfg JwtAuthConfig 21 | alg jwt.Algorithm 22 | } 23 | 24 | var _ Auth = (*JwtAuth)(nil) 25 | 26 | func NewJwtAuth(cfg ...JwtAuthConfig) (a *JwtAuth, err error) { 27 | a = &JwtAuth{} 28 | a.cfg = opt.Opt(cfg) 29 | 30 | if a.cfg.Lifetime == 0 { 31 | a.cfg.Lifetime = 30 * time.Minute 32 | } 33 | if a.cfg.SigningKey == "" { 34 | a.cfg.SigningKey, err = getRandString(128) 35 | if err != nil { 36 | return 37 | } 38 | } 39 | 40 | a.alg = jwt.HmacSha512(a.cfg.SigningKey) 41 | 42 | return 43 | } 44 | 45 | func (a *JwtAuth) GetToken(ident string) (token string, exp time.Time, err error) { 46 | // Currently, the generation of JWTs in multiple go routines at 47 | // the same time will result in a panic. 48 | // See: https://github.com/robbert229/jwt/issues/17 49 | // TODO: Switch to a better JWT implementation 50 | a.mx.Lock() 51 | defer a.mx.Unlock() 52 | 53 | exp = time.Now().Add(a.cfg.Lifetime) 54 | claims := jwt.NewClaim() 55 | claims.Set("sub", ident) 56 | claims.SetTime("exp", exp) 57 | token, err = a.alg.Encode(claims) 58 | return 59 | } 60 | 61 | func (a *JwtAuth) Validate(token string) (ident string, err error) { 62 | claims, err := a.alg.DecodeAndValidate(token) 63 | if err != nil { 64 | return 65 | } 66 | exp, err := claims.GetTime("exp") 67 | if err != nil || time.Now().After(exp) { 68 | err = ErrExpired 69 | return 70 | } 71 | identV, err := claims.Get("sub") 72 | if err != nil { 73 | return 74 | } 75 | ident, ok := identV.(string) 76 | if !ok { 77 | err = ErrEmptyIdent 78 | } 79 | return 80 | } 81 | 82 | func getRandString(ln int) (r string, err error) { 83 | d, err := random.GetRandByteArray(128) 84 | if err != nil { 85 | return 86 | } 87 | r = string(d) 88 | return 89 | } 90 | -------------------------------------------------------------------------------- /web/src/components/init/Init.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import styled from "styled-components"; 3 | import Button from "../Button"; 4 | import Input from "../Input"; 5 | import Label from "../Label"; 6 | 7 | interface Props { 8 | onSubmit: (key: string, password: string) => void; 9 | } 10 | 11 | const Section = styled.section` 12 | margin-top: 1em; 13 | `; 14 | 15 | const StyledButton = styled(Button)` 16 | width: 100%; 17 | margin-top: 1em; 18 | `; 19 | 20 | export const Init: React.FC = ({ onSubmit }) => { 21 | const [key, setKey] = useState(""); 22 | const [password, setPassword] = useState(""); 23 | const [passwordRep, setpasswordRep] = useState(""); 24 | 25 | const valid = !!key && !!password && password === passwordRep; 26 | 27 | return ( 28 | <> 29 |

Initialization

30 |

31 | The app needs to be initialized. Therefore, enter the key which is 32 | displayed in the log output of the backend application as well as a 33 | password you want to use to log in. 34 |

35 |
36 | 37 | setKey(e.currentTarget.value)} 42 | /> 43 |
44 |
45 | 46 | setPassword(e.currentTarget.value)} 51 | /> 52 |
53 |
54 | 55 | setpasswordRep(e.currentTarget.value)} 60 | /> 61 |
62 |
63 | onSubmit(key, password)}> 64 | Initialize 65 | 66 |
67 | 68 | ); 69 | }; 70 | -------------------------------------------------------------------------------- /web/src/hooks/useDevice.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useNavigate } from "react-router"; 3 | import { APIError } from "../api/errors"; 4 | import { Device } from "../api/models"; 5 | import { ClientInstance } from "../services/api"; 6 | import useAuth from "./useAuth"; 7 | import { useSnackbar } from "./useSnackbar"; 8 | 9 | export default function useDevice(uid: string) { 10 | const [device, setDevice] = useState({ uid } as Device); 11 | const { logout } = useAuth(true); 12 | const { show } = useSnackbar(); 13 | const nav = useNavigate(); 14 | 15 | const _isNew = uid === "new"; 16 | 17 | const update = (dev: Partial) => { 18 | setDevice({ ...Object.assign(device, dev) }); 19 | }; 20 | 21 | const commit = () => { 22 | const action = _isNew ? ClientInstance.create : ClientInstance.update; 23 | action 24 | .call(ClientInstance, device!) 25 | .then(() => { 26 | nav("/"); 27 | show("success", `Device has been ${_isNew ? "created" : "updated"}.`); 28 | }) 29 | .catch((err) => { 30 | if (err instanceof APIError) { 31 | show("error", `${err.code}: ${err.message}`); 32 | } 33 | }); 34 | }; 35 | 36 | const remove = () => { 37 | ClientInstance.delete(device.uid) 38 | .then(() => { 39 | nav("/"); 40 | show("success", "Device has been removed."); 41 | }) 42 | .catch((err) => { 43 | if (err instanceof APIError) { 44 | show("error", `${err.code}: ${err.message}`); 45 | } 46 | }); 47 | }; 48 | 49 | useEffect(() => { 50 | if (_isNew) return; 51 | 52 | ClientInstance.get(uid) 53 | .then((res) => setDevice(res)) 54 | .catch((err) => { 55 | if (err instanceof APIError) { 56 | if (err.code === 401) { 57 | logout(); 58 | } else if (err.code === 404) { 59 | show("error", "Device not found."); 60 | } else { 61 | show("error", `${err.code}: ${err.message}`); 62 | } 63 | } 64 | }); 65 | }, []); 66 | 67 | return { device, update, commit, remove }; 68 | } 69 | -------------------------------------------------------------------------------- /cmd/webwol/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "os" 6 | "time" 7 | 8 | "github.com/sirupsen/logrus" 9 | "github.com/zekroTJA/shinpuru/pkg/random" 10 | "github.com/zekrotja/webwol/internal/config" 11 | "github.com/zekrotja/webwol/internal/database" 12 | "github.com/zekrotja/webwol/internal/database/nuts" 13 | "github.com/zekrotja/webwol/internal/singleton" 14 | "github.com/zekrotja/webwol/internal/web" 15 | "github.com/zekrotja/webwol/pkg/auth" 16 | "github.com/zekrotja/webwol/pkg/hasher" 17 | ) 18 | 19 | func main() { 20 | var ( 21 | cfgFile string 22 | noColor bool 23 | ) 24 | 25 | f := flag.NewFlagSet("webwol", flag.ExitOnError) 26 | f.StringVar(&cfgFile, "c", "config.yml", "The config file location") 27 | f.BoolVar(&noColor, "nc", false, "force disable colors log output") 28 | f.Parse(os.Args[1:]) 29 | 30 | logrus.SetFormatter(&logrus.TextFormatter{ 31 | ForceColors: !noColor, 32 | FullTimestamp: true, 33 | TimestampFormat: "2006/01/02 15:04:05 MST", 34 | }) 35 | 36 | cfg := config.NewPaerser(cfgFile) 37 | if err := cfg.Load(); err != nil { 38 | logrus.WithError(err).Fatal("loading config failed") 39 | } 40 | 41 | db, err := nuts.New("data/db") 42 | if err != nil { 43 | logrus.WithError(err).Fatal("initializing database failed") 44 | } 45 | defer db.Close() 46 | 47 | checkInitialization(db) 48 | 49 | auth, err := auth.NewJwtAuth(auth.JwtAuthConfig{ 50 | SigningKey: cfg.Instance().JWTSigningKey, 51 | Lifetime: time.Duration(cfg.Instance().JWTLifetimeSeconds) * time.Second, 52 | }) 53 | if err != nil { 54 | logrus.WithError(err).Fatal("initializing auth failed") 55 | } 56 | 57 | hsh := hasher.NewArgon2ID() 58 | 59 | ws, err := web.New(cfg, db, auth, hsh) 60 | if err != nil { 61 | logrus.WithError(err).Fatal("initializing webserver failed") 62 | } 63 | 64 | if err = ws.ListenAndServe(); err != nil { 65 | logrus.WithError(err).Fatal("starting webserver failed") 66 | } 67 | } 68 | 69 | func checkInitialization(db database.Database) { 70 | pwHash, err := db.GetRootPWHash() 71 | if err != nil && !database.IsErrDatabaseNotFound(err) { 72 | logrus.WithError(err).Fatal("initialization check failed") 73 | } 74 | 75 | if len(pwHash) != 0 { 76 | return 77 | } 78 | 79 | singleton.InitVerificationKey, err = random.GetRandBase64Str(32) 80 | if err != nil && !database.IsErrDatabaseNotFound(err) { 81 | logrus.WithError(err).Fatal("key generation failed") 82 | } 83 | 84 | logrus. 85 | WithField("key", singleton.InitVerificationKey). 86 | Warn("instance needs to be initialized") 87 | } 88 | -------------------------------------------------------------------------------- /internal/web/controllers/auth.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/zekrotja/webwol/internal/database" 6 | "github.com/zekrotja/webwol/internal/singleton" 7 | "github.com/zekrotja/webwol/pkg/auth" 8 | "github.com/zekrotja/webwol/pkg/hasher" 9 | "github.com/zekrotja/webwol/pkg/models" 10 | ) 11 | 12 | type AuthController struct { 13 | auth auth.Auth 14 | hasher hasher.Hasher 15 | db database.Database 16 | } 17 | 18 | func NewAuthController( 19 | router fiber.Router, 20 | ath auth.Auth, 21 | hasher hasher.Hasher, 22 | db database.Database, 23 | ) (c *AuthController) { 24 | c = &AuthController{ath, hasher, db} 25 | 26 | router.Get("/isinitialized", c.isInitialized) 27 | router.Post("/initialize", c.initialize) 28 | router.Post("/login", c.login) 29 | 30 | return 31 | } 32 | 33 | func (c AuthController) isInitialized(ctx *fiber.Ctx) (err error) { 34 | var res models.InitializedResponse 35 | res.IsInitialiezd = singleton.InitVerificationKey == "" 36 | 37 | return ctx.JSON(res) 38 | } 39 | 40 | func (c *AuthController) initialize(ctx *fiber.Ctx) (err error) { 41 | if singleton.InitVerificationKey == "" { 42 | return fiber.NewError(fiber.StatusBadRequest, "already initialized") 43 | } 44 | 45 | var req models.InitializationRequest 46 | if err = ctx.BodyParser(&req); err != nil { 47 | return 48 | } 49 | 50 | if req.Password == "" { 51 | return fiber.NewError(fiber.StatusBadRequest, "empty password") 52 | } 53 | 54 | if req.Key != singleton.InitVerificationKey { 55 | return fiber.ErrForbidden 56 | } 57 | 58 | pwHsh, err := c.hasher.Generate(req.Password) 59 | if err != nil { 60 | return 61 | } 62 | 63 | if err = c.db.SetRootPWHash([]byte(pwHsh)); err != nil { 64 | return 65 | } 66 | 67 | singleton.InitVerificationKey = "" 68 | 69 | return ctx.JSON(models.StatusOK) 70 | } 71 | 72 | func (c *AuthController) login(ctx *fiber.Ctx) (err error) { 73 | var pw models.LoginRequest 74 | if err = ctx.BodyParser(&pw); err != nil { 75 | return 76 | } 77 | 78 | if pw.Password == "" { 79 | return fiber.ErrBadRequest 80 | } 81 | 82 | pwHash, err := c.db.GetRootPWHash() 83 | if err != nil && !database.IsErrDatabaseNotFound(err) { 84 | return 85 | } 86 | 87 | if len(pwHash) == 0 { 88 | return fiber.NewError(fiber.StatusBadRequest, "not initialized") 89 | } 90 | 91 | ok, err := c.hasher.Equal(pw.Password, string(pwHash)) 92 | if err != nil { 93 | return 94 | } 95 | 96 | if !ok { 97 | return fiber.ErrUnauthorized 98 | } 99 | 100 | var token models.TokenResponse 101 | token.Token, token.Expires, err = c.auth.GetToken("root") 102 | if err != nil { 103 | return 104 | } 105 | 106 | return ctx.JSON(token) 107 | } 108 | -------------------------------------------------------------------------------- /web/src/components/device/Device.tsx: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | import { Device, DeviceType } from "../../api/models"; 3 | import { Pingstate } from "../pingstate"; 4 | import usePing from "../../hooks/usePing"; 5 | import Button from "../Button"; 6 | import { useState } from "react"; 7 | import { Container } from "./Container"; 8 | 9 | import { ReactComponent as PCIcon } from "../../assets/pc.svg"; 10 | import { ReactComponent as MobileIcon } from "../../assets/mobile.svg"; 11 | import { ReactComponent as ServerIcon } from "../../assets/server.svg"; 12 | import { ReactComponent as IOTIcon } from "../../assets/bulb.svg"; 13 | 14 | interface Props { 15 | device: Device; 16 | onWakeUp: () => Promise; 17 | onEdit: () => void; 18 | } 19 | 20 | const Heading = styled.h3` 21 | margin: 0; 22 | `; 23 | 24 | const DeviceContainer = styled(Container)` 25 | display: flex; 26 | svg { 27 | height: 4em; 28 | margin-right: 0.5em; 29 | } 30 | background-color: ${(p) => p.theme.backgtroundDark}; 31 | padding: 0.5em; 32 | `; 33 | 34 | const Smol = styled.span` 35 | font-size: 0.9em; 36 | opacity: 0.75; 37 | `; 38 | 39 | const Controls = styled.div` 40 | margin-left: auto; 41 | align-items: center; 42 | `; 43 | 44 | export const DeviceElement: React.FC = ({ 45 | device, 46 | onWakeUp, 47 | onEdit, 48 | }) => { 49 | const { state, refresh } = usePing(device); 50 | const [wakeupDisabled, setWakeupDisabled] = useState(false); 51 | 52 | const typeLogo = (() => { 53 | switch (device.type) { 54 | case DeviceType.MOBILE: 55 | return ; 56 | case DeviceType.SERVER: 57 | return ; 58 | case DeviceType.IOT: 59 | return ; 60 | default: 61 | return ; 62 | } 63 | })(); 64 | 65 | const _onWakeUp = () => { 66 | setWakeupDisabled(true); 67 | onWakeUp() 68 | .then(() => refresh(5000)) 69 | .finally(() => setWakeupDisabled(false)); 70 | }; 71 | 72 | const _onEdit: React.MouseEventHandler = (e) => { 73 | if ((e.target as HTMLElement).id === "wakeup-button") return; 74 | onEdit(); 75 | }; 76 | 77 | return ( 78 | 79 |
{typeLogo}
80 |
81 | 82 | {device.name ?? device.ip_address ?? device.mac_address} 83 | 84 | {device.mac_address} 85 | {device.ip_address && } 86 |
87 | 88 | {state !== true && ( 89 | 96 | )} 97 | 98 |
99 | ); 100 | }; 101 | -------------------------------------------------------------------------------- /internal/database/nuts/nuts.go: -------------------------------------------------------------------------------- 1 | package nuts 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/xujiajun/nutsdb" 7 | "github.com/zekrotja/webwol/internal/database" 8 | "github.com/zekrotja/webwol/pkg/models" 9 | ) 10 | 11 | const ( 12 | bucketGeneral = "general" 13 | bucketDevices = "devices" 14 | ) 15 | 16 | type NutsDB struct { 17 | db *nutsdb.DB 18 | } 19 | 20 | var _ database.Database = (*NutsDB)(nil) 21 | 22 | func New(dir string) (n *NutsDB, err error) { 23 | n = &NutsDB{} 24 | 25 | opts := nutsdb.DefaultOptions 26 | opts.Dir = dir 27 | n.db, err = nutsdb.Open(opts) 28 | 29 | return 30 | } 31 | 32 | func (n *NutsDB) Close() error { 33 | return n.db.Close() 34 | } 35 | 36 | func (n *NutsDB) SetRootPWHash(hsh []byte) (err error) { 37 | err = n.db.Update(func(tx *nutsdb.Tx) error { 38 | return tx.Put(bucketGeneral, []byte("root-pw"), hsh, 0) 39 | }) 40 | return 41 | } 42 | 43 | func (n *NutsDB) GetRootPWHash() (hsh []byte, err error) { 44 | err = n.db.View(func(tx *nutsdb.Tx) error { 45 | r, err := tx.Get(bucketGeneral, []byte("root-pw")) 46 | if err != nil { 47 | return n.wrapErr(err) 48 | } 49 | hsh = r.Value 50 | return nil 51 | }) 52 | return 53 | } 54 | 55 | func (n *NutsDB) ListDevices() (ds []*models.Device, err error) { 56 | var el nutsdb.Entries 57 | err = n.db.View(func(tx *nutsdb.Tx) error { 58 | el, err = tx.GetAll(bucketDevices) 59 | return n.wrapErr(err) 60 | }) 61 | if err != nil { 62 | return 63 | } 64 | 65 | ds = make([]*models.Device, len(el)) 66 | for i, e := range el { 67 | var d models.Device 68 | if d, err = models.UnmarshalDevice(e.Value); err != nil { 69 | return 70 | } 71 | ds[i] = &d 72 | } 73 | 74 | return 75 | } 76 | 77 | func (n *NutsDB) GetDevice(id string) (dp *models.Device, err error) { 78 | err = n.db.View(func(tx *nutsdb.Tx) error { 79 | entry, err := tx.Get(bucketDevices, []byte(id)) 80 | if err != nil { 81 | return n.wrapErr(err) 82 | } 83 | d, err := models.UnmarshalDevice(entry.Value) 84 | if err != nil { 85 | return err 86 | } 87 | dp = &d 88 | return nil 89 | }) 90 | return 91 | } 92 | 93 | func (n *NutsDB) SetDevice(d *models.Device) (err error) { 94 | rd, err := d.Marshal() 95 | if err != nil { 96 | return 97 | } 98 | err = n.db.Update(func(tx *nutsdb.Tx) error { 99 | return tx.Put(bucketDevices, []byte(d.UID), rd, 0) 100 | }) 101 | return 102 | } 103 | 104 | func (n *NutsDB) RemoveDevice(id string) (err error) { 105 | err = n.db.Update(func(tx *nutsdb.Tx) error { 106 | return tx.Delete(bucketDevices, []byte(id)) 107 | }) 108 | return 109 | } 110 | 111 | func (n *NutsDB) wrapErr(err error) error { 112 | if err == nil { 113 | return nil 114 | } 115 | if err == nutsdb.ErrBucket || 116 | strings.HasPrefix(err.Error(), "not found bucket:") || 117 | err == nutsdb.ErrBucketEmpty { 118 | return database.ErrNotFound 119 | } 120 | return err 121 | } 122 | -------------------------------------------------------------------------------- /web/src/api/api.ts: -------------------------------------------------------------------------------- 1 | import { APIError } from "./errors"; 2 | import { 3 | Device, 4 | ErrorReponse, 5 | InitializedResponse, 6 | PingResponse, 7 | Status, 8 | TokenResponse, 9 | } from "./models"; 10 | 11 | type HttpMethod = "GET" | "POST" | "DELETE" | "PUT"; 12 | 13 | export class Client { 14 | private token: string | undefined; 15 | private _onUnauthorized: ((err: APIError) => boolean) | undefined; 16 | 17 | constructor(private baseUrl: string = "/api") {} 18 | 19 | // --- AUTH --- 20 | 21 | get isLoggedIn(): boolean { 22 | return !!this.token; 23 | } 24 | 25 | set onUnauthorized(handler: (err: APIError) => boolean) { 26 | this._onUnauthorized = handler; 27 | } 28 | 29 | isInitialized(): Promise { 30 | return this.req("GET", "auth/isinitialized"); 31 | } 32 | 33 | initialize(key: string, password: string): Promise { 34 | return this.req("POST", "auth/initialize", { key, password }); 35 | } 36 | 37 | async login(password: string): Promise<{}> { 38 | const res = (await this.req("POST", "auth/login", { 39 | password, 40 | })) as TokenResponse; 41 | this.token = `bearer ${res.token}`; 42 | return {}; 43 | } 44 | 45 | logout() { 46 | this.token = undefined; 47 | } 48 | 49 | // --- DEVICES --- 50 | 51 | list(): Promise { 52 | return this.req("GET", "devices"); 53 | } 54 | 55 | get(id: string): Promise { 56 | return this.req("GET", `devices/${id}`); 57 | } 58 | 59 | create(dev: Device): Promise { 60 | return this.req("POST", "devices", dev); 61 | } 62 | 63 | update(dev: Device): Promise { 64 | return this.req("POST", `devices/${dev.uid}`, dev); 65 | } 66 | 67 | delete(id: string): Promise { 68 | return this.req("DELETE", `devices/${id}`); 69 | } 70 | 71 | wake(id: string): Promise { 72 | return this.req("POST", `devices/${id}/wake`); 73 | } 74 | 75 | ping(id: string): Promise { 76 | return this.req("POST", `devices/${id}/ping`); 77 | } 78 | 79 | // --- UTIL --- 80 | 81 | private async req( 82 | method: HttpMethod, 83 | path: string, 84 | body?: object 85 | ): Promise { 86 | const headers: { [key: string]: string } = { 87 | "content-type": "application/json", 88 | }; 89 | 90 | if (!!this.token) headers["authorization"] = this.token; 91 | 92 | path = path.replace("//", "/"); 93 | 94 | const res = await window.fetch(`${this.baseUrl}/${path}`, { 95 | method, 96 | headers, 97 | credentials: "include", 98 | body: body ? JSON.stringify(body) : undefined, 99 | }); 100 | 101 | if (res.status === 204) { 102 | return {} as T; 103 | } 104 | 105 | let data = {}; 106 | 107 | try { 108 | data = await res.json(); 109 | } catch {} 110 | 111 | if (!res.ok) { 112 | const err = new APIError(res, data as ErrorReponse); 113 | if (res.status === 401) 114 | if (this._onUnauthorized?.call(this, err) === false) return {} as T; 115 | throw err; 116 | } 117 | 118 | return data as T; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /internal/web/controllers/devices.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "github.com/rs/xid" 6 | "github.com/zekrotja/webwol/internal/database" 7 | "github.com/zekrotja/webwol/internal/util" 8 | "github.com/zekrotja/webwol/pkg/imcp" 9 | "github.com/zekrotja/webwol/pkg/models" 10 | "github.com/zekrotja/webwol/pkg/wol" 11 | ) 12 | 13 | type DevicseController struct { 14 | db database.Database 15 | } 16 | 17 | func NewDeviceController( 18 | router fiber.Router, 19 | db database.Database, 20 | ) (c *DevicseController) { 21 | c = &DevicseController{db} 22 | 23 | router.Get("", c.list) 24 | router.Post("", c.addUpdate) 25 | router.Get("/:id", c.get) 26 | router.Post("/:id", c.addUpdate) 27 | router.Delete("/:id", c.delete) 28 | router.Post("/:id/wake", c.wake) 29 | router.Post("/:id/ping", c.ping) 30 | 31 | return 32 | } 33 | 34 | func (c *DevicseController) list(ctx *fiber.Ctx) (err error) { 35 | devs, err := c.db.ListDevices() 36 | if err != nil && !database.IsErrDatabaseNotFound(err) { 37 | return 38 | } 39 | return ctx.JSON(util.Unnilify(devs)) 40 | } 41 | 42 | func (c *DevicseController) get(ctx *fiber.Ctx) (err error) { 43 | id := ctx.Params("id") 44 | 45 | dev, err := c.db.GetDevice(id) 46 | if err != nil { 47 | return 48 | } 49 | 50 | return ctx.JSON(dev) 51 | } 52 | 53 | func (c *DevicseController) addUpdate(ctx *fiber.Ctx) (err error) { 54 | id := ctx.Params("id") 55 | if id == "" { 56 | id = xid.New().String() 57 | } else if _, err = c.db.GetDevice(id); err != nil { 58 | return 59 | } 60 | 61 | var dev models.Device 62 | if err = ctx.BodyParser(&dev); err != nil { 63 | return 64 | } 65 | if err = dev.Validate(); err != nil { 66 | return fiber.NewError(fiber.StatusBadRequest, err.Error()) 67 | } 68 | 69 | dev.UID = id 70 | if err = c.db.SetDevice(&dev); err != nil { 71 | return 72 | } 73 | 74 | return ctx.JSON(dev) 75 | } 76 | 77 | func (c *DevicseController) delete(ctx *fiber.Ctx) (err error) { 78 | id := ctx.Params("id") 79 | 80 | if err = c.db.RemoveDevice(id); err != nil { 81 | return 82 | } 83 | 84 | return ctx.JSON(models.StatusOK) 85 | } 86 | 87 | func (c *DevicseController) wake(ctx *fiber.Ctx) (err error) { 88 | id := ctx.Params("id") 89 | 90 | dev, err := c.db.GetDevice(id) 91 | if err != nil { 92 | return 93 | } 94 | 95 | if err = wol.SendMagicPacket(dev.MacAddress, ""); err != nil { 96 | return 97 | } 98 | 99 | return ctx.JSON(models.StatusOK) 100 | } 101 | 102 | func (c *DevicseController) ping(ctx *fiber.Ctx) (err error) { 103 | id := ctx.Params("id") 104 | repeat, err := util.QueryInt(ctx, "repeat", 1) 105 | if err != nil { 106 | return fiber.ErrBadRequest 107 | } 108 | 109 | dev, err := c.db.GetDevice(id) 110 | if err != nil { 111 | return 112 | } 113 | 114 | if dev.IPAddress == "" { 115 | return fiber.NewError(fiber.StatusBadRequest, "device has no ip address") 116 | } 117 | 118 | var res models.PingResponse 119 | stats, err := imcp.Ping(dev.IPAddress, repeat) 120 | res.Successful = err == nil && stats.PacketLoss < 100 121 | res.RTT = stats.AvgRtt.Microseconds() 122 | res.SuccessRate = 1 - float32(stats.PacketLoss)/100 123 | 124 | return ctx.JSON(res) 125 | } 126 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | paths-ignore: 8 | - "**.md" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | 13 | docker: 14 | name: Build Docker Image 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | goos: 19 | - linux 20 | goarch: 21 | - amd64 22 | - arm64 23 | steps: 24 | - name: Check out code 25 | uses: actions/checkout@v2 26 | - name: Log in to ghcr 27 | uses: docker/login-action@v1 28 | with: 29 | registry: ghcr.io 30 | username: ${{ github.actor }} 31 | password: ${{ secrets.GITHUB_TOKEN }} 32 | - name: Build & Push 33 | uses: docker/build-push-action@v2 34 | with: 35 | context: ./ 36 | push: true 37 | no-cache: true 38 | build-args: | 39 | GOOS=${{ matrix.goos }} 40 | GOARCH=${{ matrix.goarch }} 41 | tags: | 42 | ghcr.io/zekrotja/webwol:${{ matrix.goarch }} 43 | 44 | frontend: 45 | name: Build Frontend Artifacts 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: Set up NodeJS 49 | uses: actions/setup-node@v1 50 | with: 51 | node-version: '14.x' 52 | - name: Check out code 53 | uses: actions/checkout@v2 54 | - name: Install dependencies 55 | working-directory: web 56 | run: yarn 57 | - name: Build Web App 58 | working-directory: web 59 | run: yarn run build 60 | - name: Upload Artifcats 61 | uses: actions/upload-artifact@v2 62 | with: 63 | name: frontend 64 | path: web/dist 65 | retention-days: 1 66 | 67 | backend: 68 | name: Build Backend Artifacts 69 | runs-on: ubuntu-latest 70 | needs: 71 | - frontend 72 | strategy: 73 | matrix: 74 | goos: 75 | - linux 76 | - windows 77 | - darwin 78 | goarch: 79 | - amd64 80 | - arm64 81 | steps: 82 | - name: Set up Go 83 | uses: actions/setup-go@v1 84 | with: 85 | go-version: '1.18rc1' 86 | - name: Check out code 87 | uses: actions/checkout@v2 88 | - name: Retrieve frontend files 89 | uses: actions/download-artifact@v2 90 | with: 91 | name: frontend 92 | path: internal/embedded/webdist 93 | - name: Get dependencies 94 | run: go get -v ./... 95 | - name: Build Backend (${{ matrix.goos }}-${{ matrix.goarch }}) 96 | env: 97 | GOOS: ${{ matrix.goos }} 98 | GOARCH: ${{ matrix.goarch }} 99 | run: go build -o ./bin/webwol-${{ matrix.goos }}-${{ matrix.goarch }} ./cmd/webwol/main.go 100 | - name: Rename Windows Binary 101 | if: ${{ matrix.goos == 'windows' }} 102 | env: 103 | FNAME: ./bin/webwol-${{ matrix.goos }}-${{ matrix.goarch }} 104 | run: mv ${{ env.FNAME }} ${{ env.FNAME }}.exe 105 | - name: Upload Artifcats 106 | uses: actions/upload-artifact@v2 107 | with: 108 | name: backend 109 | path: bin/ -------------------------------------------------------------------------------- /internal/web/web.go: -------------------------------------------------------------------------------- 1 | package web 2 | 3 | import ( 4 | "errors" 5 | "io/fs" 6 | "net/http" 7 | "os" 8 | 9 | "github.com/gofiber/fiber/v2" 10 | "github.com/gofiber/fiber/v2/middleware/cors" 11 | "github.com/gofiber/fiber/v2/middleware/filesystem" 12 | "github.com/sirupsen/logrus" 13 | "github.com/zekrotja/webwol/internal/config" 14 | "github.com/zekrotja/webwol/internal/database" 15 | "github.com/zekrotja/webwol/internal/embedded" 16 | "github.com/zekrotja/webwol/internal/web/controllers" 17 | "github.com/zekrotja/webwol/internal/web/middleware" 18 | "github.com/zekrotja/webwol/pkg/auth" 19 | "github.com/zekrotja/webwol/pkg/hasher" 20 | "github.com/zekrotja/webwol/pkg/models" 21 | ) 22 | 23 | var ( 24 | syntaxError = &fiber.SyntaxError{} 25 | ) 26 | 27 | type WebServer struct { 28 | cfg config.Config 29 | auth auth.Auth 30 | 31 | app *fiber.App 32 | } 33 | 34 | func New( 35 | cfg config.Config, 36 | db database.Database, 37 | auth auth.Auth, 38 | hasher hasher.Hasher, 39 | ) (ws *WebServer, err error) { 40 | ws = &WebServer{} 41 | ws.cfg = cfg 42 | ws.app = fiber.New(fiber.Config{ 43 | ErrorHandler: ws.errorHandler, 44 | DisableStartupMessage: true, 45 | }) 46 | 47 | if co := ws.cfg.Instance().CorsOrigins; co != "" { 48 | ws.app.Use(cors.New(cors.Config{ 49 | AllowOrigins: co, 50 | AllowMethods: "GET,POST,DELETE,PUT,OPTIONS", 51 | AllowHeaders: "content-type,accept,authorization", 52 | AllowCredentials: true, 53 | })) 54 | } 55 | 56 | api := ws.app.Group("/api") 57 | 58 | controllers.NewAuthController(api.Group("/auth"), auth, hasher, db) 59 | controllers.NewDeviceController(api.Group("/devices", middleware.Auth(auth)), db) 60 | 61 | fs, err := getFS() 62 | if err != nil { 63 | return 64 | } 65 | ws.app.Use(filesystem.New(filesystem.Config{ 66 | Root: fs, 67 | Browse: true, 68 | Index: "index.html", 69 | MaxAge: 3600, 70 | NotFoundFile: "index.html", 71 | })) 72 | 73 | return 74 | } 75 | 76 | func (ws *WebServer) ListenAndServe() error { 77 | addr := ws.cfg.Instance().BindAddress 78 | logrus.WithField("addr", addr).Info("webserver listening ...") 79 | return ws.app.Listen(addr) 80 | } 81 | 82 | func (ws *WebServer) errorHandler(ctx *fiber.Ctx, err error) error { 83 | if errors.As(err, &syntaxError) { 84 | return ws.errorHandler(ctx, 85 | fiber.NewError(fiber.StatusBadRequest, err.Error())) 86 | } else if database.IsErrDatabaseNotFound(err) { 87 | return ws.errorHandler(ctx, fiber.ErrNotFound) 88 | } else if fErr, ok := err.(*fiber.Error); ok { 89 | if fErr == fiber.ErrUnprocessableEntity { 90 | fErr = fiber.ErrBadRequest 91 | } 92 | 93 | ctx.Status(fErr.Code) 94 | return ctx.JSON(&models.Error{ 95 | Error: fErr.Message, 96 | Code: fErr.Code, 97 | }) 98 | } 99 | 100 | return ws.errorHandler(ctx, 101 | fiber.NewError(fiber.StatusInternalServerError, err.Error())) 102 | } 103 | 104 | func getFS() (f http.FileSystem, err error) { 105 | fsys, err := fs.Sub(embedded.FrontendFiles, "webdist") 106 | if err != nil { 107 | return 108 | } 109 | _, err = fsys.Open("index.html") 110 | if os.IsNotExist(err) { 111 | logrus.Info("using web files form web/dist") 112 | f = http.Dir("web/dist") 113 | err = nil 114 | return 115 | } 116 | if err != nil { 117 | return 118 | } 119 | logrus.Info("using embedded web files") 120 | f = http.FS(fsys) 121 | return 122 | } 123 | -------------------------------------------------------------------------------- /web/src/routes/Device.tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from "react-router"; 2 | import styled from "styled-components"; 3 | import Button from "../components/Button"; 4 | import Input from "../components/Input"; 5 | import Label from "../components/Label"; 6 | import Select from "../components/Select"; 7 | import { SkeletonTile } from "../components/skeleton"; 8 | import useDevice from "../hooks/useDevice"; 9 | 10 | const Container = styled.div` 11 | max-width: 25em; 12 | margin: 0 auto; 13 | `; 14 | 15 | const Section = styled.section` 16 | margin-bottom: 1em; 17 | `; 18 | 19 | const ControlSection = styled(Section)` 20 | margin-top: 2em; 21 | `; 22 | 23 | const WideButton = styled(Button)` 24 | width: 100%; 25 | `; 26 | 27 | const DeleteButton = styled(WideButton)` 28 | margin-top: 1em; 29 | background: ${(p) => p.theme.redGrad}; 30 | `; 31 | 32 | export const DeviceRoute: React.FC = () => { 33 | const { uid } = useParams(); 34 | const { device, update, commit, remove } = useDevice(uid!); 35 | 36 | const name = 37 | n(device.name) ?? 38 | n(device.ip_address) ?? 39 | n(device.mac_address) ?? 40 | "New Device"; 41 | 42 | const _isNew = uid !== "new"; 43 | 44 | return ( 45 | 46 | {!!device ? ( 47 | <> 48 |

{name}

49 |
50 | 51 | update({ name: e.currentTarget.value })} 55 | /> 56 |
57 |
58 | 59 | 70 |
71 |
72 | 73 | update({ mac_address: e.currentTarget.value })} 77 | /> 78 |
79 |
80 | 81 | update({ ip_address: e.currentTarget.value })} 85 | /> 86 |
87 | 88 | commit()}>Save 89 | {_isNew && ( 90 | remove()}>Delete 91 | )} 92 | 93 | 94 | ) : ( 95 | <> 96 | 97 | 103 | 109 | 115 | 116 | )} 117 |
118 | ); 119 | }; 120 | 121 | function n(s: string): string | null { 122 | if (!s) return null; 123 | return s; 124 | } 125 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU= 3 | github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 4 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 5 | github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= 6 | github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= 7 | github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 h1:loy0fjI90vF44BPW4ZYOkE3tDkGTy7yHURusOJimt+I= 8 | github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387/go.mod h1:GuR5j/NW7AU7tDAQUDGCtpiPxWIOy/c3kiRDnlwiCHc= 9 | github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= 10 | github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 11 | github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= 12 | github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= 13 | github.com/coreos/bbolt v1.3.1-coreos.6.0.20180223184059-4f5275f4ebbf/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534 h1:dhy9OQKGBh4zVXbjwbxxHjRxMJtLXj3zfgpBYQaR4Q4= 18 | github.com/go-ping/ping v0.0.0-20211130115550-779d1e919534/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= 19 | github.com/gofiber/fiber/v2 v2.27.0 h1:u34t1nOea7zz4jcZDK7+ZMiG+MVFYrHqMhTdYQDiFA8= 20 | github.com/gofiber/fiber/v2 v2.27.0/go.mod h1:0bPXdTu+jRqINrEq1T6mHeVBnE0lQd67PGu35jD3hLk= 21 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 22 | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 23 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 24 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 25 | github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 26 | github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= 27 | github.com/jessevdk/go-flags v0.0.0-20150816100521-1acbbaff2f34/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 28 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 29 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 30 | github.com/klauspost/compress v1.14.1 h1:hLQYb23E8/fO+1u53d02A97a8UnsddcvYzq4ERRU4ds= 31 | github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 32 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 33 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 34 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 35 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 36 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 37 | github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= 38 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 39 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 40 | github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 41 | github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 42 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 43 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 44 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 45 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 46 | github.com/robbert229/jwt v2.0.0+incompatible h1:5Pc2FCpA2ahofO4QrWzXXQc0RZYfrZu0TSWHLcTOLz0= 47 | github.com/robbert229/jwt v2.0.0+incompatible/go.mod h1:I0pqJYBbhfQce4mJL2X6pYnk3T1oaAuF2ou8rSWpMBo= 48 | github.com/rs/xid v1.3.0 h1:6NjYksEUlhurdVehpc7S7dk6DAmcKv8V9gG0FsVN2U4= 49 | github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 50 | github.com/sabhiram/go-colorize v0.0.0-20210403184538-366f55d711cf/go.mod h1:GvlEbMJBpbAXFn06UajbdBlGZ18iLvHyuIrgG//L8uk= 51 | github.com/sabhiram/go-wol v0.0.0-20211224004021-c83b0c2f887d h1:NDtoSmsxTpDYTqvUurn2ooAzDaYbJSB9/tOhLzaewgo= 52 | github.com/sabhiram/go-wol v0.0.0-20211224004021-c83b0c2f887d/go.mod h1:SVPBBd492Gk7Cq5lPd6OAYtIGk2r1FsyH8KT3IB8h7c= 53 | github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= 54 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 55 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 56 | github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 57 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 58 | github.com/stretchr/testify v0.0.0-20150929183540-2b15294402a8/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 59 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 60 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 61 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 62 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 63 | github.com/traefik/paerser v0.1.4 h1:/IXjV04Gf6di51H8Jl7jyS3OylsLjIasrwXIIwj1aT8= 64 | github.com/traefik/paerser v0.1.4/go.mod h1:FIdQ4Y92ulQUGSeZgxchtBKEcLw1o551PMNg9PoIq/4= 65 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 66 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 67 | github.com/valyala/fasthttp v1.33.0 h1:mHBKd98J5NcXuBddgjvim1i3kWzlng1SzLhrnBOU9g8= 68 | github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4= 69 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 70 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 71 | github.com/xujiajun/gorouter v1.2.0/go.mod h1:yJrIta+bTNpBM/2UT8hLOaEAFckO+m/qmR3luMIQygM= 72 | github.com/xujiajun/mmap-go v1.0.1 h1:7Se7ss1fLPPRW+ePgqGpCkfGIZzJV6JPq9Wq9iv/WHc= 73 | github.com/xujiajun/mmap-go v1.0.1/go.mod h1:CNN6Sw4SL69Sui00p0zEzcZKbt+5HtEnYUsc6BKKRMg= 74 | github.com/xujiajun/nutsdb v0.6.0 h1:voRbF4bQO6gF9xiFZ+5w/fPHgEfR7Jea1NOtU34I8yE= 75 | github.com/xujiajun/nutsdb v0.6.0/go.mod h1:Q8FXi2zeQRluPpUl/CKQ6J7u/9gcI02J6cZp3owFLyA= 76 | github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b h1:jKG9OiL4T4xQN3IUrhUpc1tG+HfDXppkgVcrAiiaI/0= 77 | github.com/xujiajun/utils v0.0.0-20190123093513-8bf096c4f53b/go.mod h1:AZd87GYJlUzl82Yab2kTjx1EyXSQCAfZDhpTo1SQC4k= 78 | github.com/zekroTJA/shinpuru v0.0.0-20220223004228-ea95cbaf5fbd h1:wsEx15kdFCzLMYWz5FYT3WuGqzZgfsPwQcALbMF6O30= 79 | github.com/zekroTJA/shinpuru v0.0.0-20220223004228-ea95cbaf5fbd/go.mod h1:h569vIhEXj/e9jM7ZkDebpBG0wkKZh5wBItniP0Olo4= 80 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 81 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 82 | golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 83 | golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 84 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE= 85 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 86 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 87 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 88 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 89 | golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 90 | golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs= 91 | golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 92 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 93 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sys v0.0.0-20181221143128-b4a75ba826a6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 95 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 96 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 97 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 98 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 101 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 102 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= 108 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 110 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 111 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 112 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 113 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 114 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 115 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 116 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 117 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 118 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 119 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 120 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 121 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 122 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= 123 | -------------------------------------------------------------------------------- /web/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@ampproject/remapping@^2.1.0": 6 | version "2.1.2" 7 | resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34" 8 | integrity sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg== 9 | dependencies: 10 | "@jridgewell/trace-mapping" "^0.3.0" 11 | 12 | "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7": 13 | version "7.16.7" 14 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" 15 | integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== 16 | dependencies: 17 | "@babel/highlight" "^7.16.7" 18 | 19 | "@babel/compat-data@^7.16.4": 20 | version "7.17.0" 21 | resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.0.tgz#86850b8597ea6962089770952075dcaabb8dba34" 22 | integrity sha512-392byTlpGWXMv4FbyWw3sAZ/FrW/DrwqLGXpy0mbyNe9Taqv1mg9yON5/o0cnr8XYCkFTZbC1eV+c+LAROgrng== 23 | 24 | "@babel/core@^7.15.5", "@babel/core@^7.16.12": 25 | version "7.17.5" 26 | resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.17.5.tgz#6cd2e836058c28f06a4ca8ee7ed955bbf37c8225" 27 | integrity sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA== 28 | dependencies: 29 | "@ampproject/remapping" "^2.1.0" 30 | "@babel/code-frame" "^7.16.7" 31 | "@babel/generator" "^7.17.3" 32 | "@babel/helper-compilation-targets" "^7.16.7" 33 | "@babel/helper-module-transforms" "^7.16.7" 34 | "@babel/helpers" "^7.17.2" 35 | "@babel/parser" "^7.17.3" 36 | "@babel/template" "^7.16.7" 37 | "@babel/traverse" "^7.17.3" 38 | "@babel/types" "^7.17.0" 39 | convert-source-map "^1.7.0" 40 | debug "^4.1.0" 41 | gensync "^1.0.0-beta.2" 42 | json5 "^2.1.2" 43 | semver "^6.3.0" 44 | 45 | "@babel/generator@^7.17.3": 46 | version "7.17.3" 47 | resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.3.tgz#a2c30b0c4f89858cb87050c3ffdfd36bdf443200" 48 | integrity sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg== 49 | dependencies: 50 | "@babel/types" "^7.17.0" 51 | jsesc "^2.5.1" 52 | source-map "^0.5.0" 53 | 54 | "@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.16.7": 55 | version "7.16.7" 56 | resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" 57 | integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== 58 | dependencies: 59 | "@babel/types" "^7.16.7" 60 | 61 | "@babel/helper-compilation-targets@^7.16.7": 62 | version "7.16.7" 63 | resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b" 64 | integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== 65 | dependencies: 66 | "@babel/compat-data" "^7.16.4" 67 | "@babel/helper-validator-option" "^7.16.7" 68 | browserslist "^4.17.5" 69 | semver "^6.3.0" 70 | 71 | "@babel/helper-environment-visitor@^7.16.7": 72 | version "7.16.7" 73 | resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" 74 | integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== 75 | dependencies: 76 | "@babel/types" "^7.16.7" 77 | 78 | "@babel/helper-function-name@^7.16.7": 79 | version "7.16.7" 80 | resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" 81 | integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== 82 | dependencies: 83 | "@babel/helper-get-function-arity" "^7.16.7" 84 | "@babel/template" "^7.16.7" 85 | "@babel/types" "^7.16.7" 86 | 87 | "@babel/helper-get-function-arity@^7.16.7": 88 | version "7.16.7" 89 | resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" 90 | integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== 91 | dependencies: 92 | "@babel/types" "^7.16.7" 93 | 94 | "@babel/helper-hoist-variables@^7.16.7": 95 | version "7.16.7" 96 | resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" 97 | integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== 98 | dependencies: 99 | "@babel/types" "^7.16.7" 100 | 101 | "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.16.7": 102 | version "7.16.7" 103 | resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" 104 | integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== 105 | dependencies: 106 | "@babel/types" "^7.16.7" 107 | 108 | "@babel/helper-module-transforms@^7.16.7": 109 | version "7.17.6" 110 | resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz#3c3b03cc6617e33d68ef5a27a67419ac5199ccd0" 111 | integrity sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA== 112 | dependencies: 113 | "@babel/helper-environment-visitor" "^7.16.7" 114 | "@babel/helper-module-imports" "^7.16.7" 115 | "@babel/helper-simple-access" "^7.16.7" 116 | "@babel/helper-split-export-declaration" "^7.16.7" 117 | "@babel/helper-validator-identifier" "^7.16.7" 118 | "@babel/template" "^7.16.7" 119 | "@babel/traverse" "^7.17.3" 120 | "@babel/types" "^7.17.0" 121 | 122 | "@babel/helper-plugin-utils@^7.16.7": 123 | version "7.16.7" 124 | resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" 125 | integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== 126 | 127 | "@babel/helper-simple-access@^7.16.7": 128 | version "7.16.7" 129 | resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7" 130 | integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g== 131 | dependencies: 132 | "@babel/types" "^7.16.7" 133 | 134 | "@babel/helper-split-export-declaration@^7.16.7": 135 | version "7.16.7" 136 | resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" 137 | integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== 138 | dependencies: 139 | "@babel/types" "^7.16.7" 140 | 141 | "@babel/helper-validator-identifier@^7.16.7": 142 | version "7.16.7" 143 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" 144 | integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== 145 | 146 | "@babel/helper-validator-option@^7.16.7": 147 | version "7.16.7" 148 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" 149 | integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== 150 | 151 | "@babel/helpers@^7.17.2": 152 | version "7.17.2" 153 | resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.17.2.tgz#23f0a0746c8e287773ccd27c14be428891f63417" 154 | integrity sha512-0Qu7RLR1dILozr/6M0xgj+DFPmi6Bnulgm9M8BVa9ZCWxDqlSnqt3cf8IDPB5m45sVXUZ0kuQAgUrdSFFH79fQ== 155 | dependencies: 156 | "@babel/template" "^7.16.7" 157 | "@babel/traverse" "^7.17.0" 158 | "@babel/types" "^7.17.0" 159 | 160 | "@babel/highlight@^7.16.7": 161 | version "7.16.10" 162 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" 163 | integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw== 164 | dependencies: 165 | "@babel/helper-validator-identifier" "^7.16.7" 166 | chalk "^2.0.0" 167 | js-tokens "^4.0.0" 168 | 169 | "@babel/parser@^7.16.7", "@babel/parser@^7.17.3": 170 | version "7.17.3" 171 | resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.3.tgz#b07702b982990bf6fdc1da5049a23fece4c5c3d0" 172 | integrity sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA== 173 | 174 | "@babel/plugin-syntax-jsx@^7.16.7": 175 | version "7.16.7" 176 | resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" 177 | integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== 178 | dependencies: 179 | "@babel/helper-plugin-utils" "^7.16.7" 180 | 181 | "@babel/plugin-transform-react-jsx-development@^7.16.7": 182 | version "7.16.7" 183 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz#43a00724a3ed2557ed3f276a01a929e6686ac7b8" 184 | integrity sha512-RMvQWvpla+xy6MlBpPlrKZCMRs2AGiHOGHY3xRwl0pEeim348dDyxeH4xBsMPbIMhujeq7ihE702eM2Ew0Wo+A== 185 | dependencies: 186 | "@babel/plugin-transform-react-jsx" "^7.16.7" 187 | 188 | "@babel/plugin-transform-react-jsx-self@^7.16.7": 189 | version "7.16.7" 190 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.16.7.tgz#f432ad0cba14c4a1faf44f0076c69e42a4d4479e" 191 | integrity sha512-oe5VuWs7J9ilH3BCCApGoYjHoSO48vkjX2CbA5bFVhIuO2HKxA3vyF7rleA4o6/4rTDbk6r8hBW7Ul8E+UZrpA== 192 | dependencies: 193 | "@babel/helper-plugin-utils" "^7.16.7" 194 | 195 | "@babel/plugin-transform-react-jsx-source@^7.16.7": 196 | version "7.16.7" 197 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.16.7.tgz#1879c3f23629d287cc6186a6c683154509ec70c0" 198 | integrity sha512-rONFiQz9vgbsnaMtQlZCjIRwhJvlrPET8TabIUK2hzlXw9B9s2Ieaxte1SCOOXMbWRHodbKixNf3BLcWVOQ8Bw== 199 | dependencies: 200 | "@babel/helper-plugin-utils" "^7.16.7" 201 | 202 | "@babel/plugin-transform-react-jsx@^7.16.7": 203 | version "7.17.3" 204 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz#eac1565da176ccb1a715dae0b4609858808008c1" 205 | integrity sha512-9tjBm4O07f7mzKSIlEmPdiE6ub7kfIe6Cd+w+oQebpATfTQMAgW+YOuWxogbKVTulA+MEO7byMeIUtQ1z+z+ZQ== 206 | dependencies: 207 | "@babel/helper-annotate-as-pure" "^7.16.7" 208 | "@babel/helper-module-imports" "^7.16.7" 209 | "@babel/helper-plugin-utils" "^7.16.7" 210 | "@babel/plugin-syntax-jsx" "^7.16.7" 211 | "@babel/types" "^7.17.0" 212 | 213 | "@babel/runtime@^7.7.6": 214 | version "7.17.2" 215 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" 216 | integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== 217 | dependencies: 218 | regenerator-runtime "^0.13.4" 219 | 220 | "@babel/template@^7.16.7": 221 | version "7.16.7" 222 | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" 223 | integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== 224 | dependencies: 225 | "@babel/code-frame" "^7.16.7" 226 | "@babel/parser" "^7.16.7" 227 | "@babel/types" "^7.16.7" 228 | 229 | "@babel/traverse@^7.17.0", "@babel/traverse@^7.17.3", "@babel/traverse@^7.4.5": 230 | version "7.17.3" 231 | resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57" 232 | integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw== 233 | dependencies: 234 | "@babel/code-frame" "^7.16.7" 235 | "@babel/generator" "^7.17.3" 236 | "@babel/helper-environment-visitor" "^7.16.7" 237 | "@babel/helper-function-name" "^7.16.7" 238 | "@babel/helper-hoist-variables" "^7.16.7" 239 | "@babel/helper-split-export-declaration" "^7.16.7" 240 | "@babel/parser" "^7.17.3" 241 | "@babel/types" "^7.17.0" 242 | debug "^4.1.0" 243 | globals "^11.1.0" 244 | 245 | "@babel/types@^7.15.6", "@babel/types@^7.16.7", "@babel/types@^7.17.0": 246 | version "7.17.0" 247 | resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" 248 | integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== 249 | dependencies: 250 | "@babel/helper-validator-identifier" "^7.16.7" 251 | to-fast-properties "^2.0.0" 252 | 253 | "@emotion/is-prop-valid@^0.8.8": 254 | version "0.8.8" 255 | resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" 256 | integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== 257 | dependencies: 258 | "@emotion/memoize" "0.7.4" 259 | 260 | "@emotion/memoize@0.7.4": 261 | version "0.7.4" 262 | resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" 263 | integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== 264 | 265 | "@emotion/stylis@^0.8.4": 266 | version "0.8.5" 267 | resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" 268 | integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== 269 | 270 | "@emotion/unitless@^0.7.4": 271 | version "0.7.5" 272 | resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" 273 | integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== 274 | 275 | "@jridgewell/resolve-uri@^3.0.3": 276 | version "3.0.5" 277 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz#68eb521368db76d040a6315cdb24bf2483037b9c" 278 | integrity sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew== 279 | 280 | "@jridgewell/sourcemap-codec@^1.4.10": 281 | version "1.4.11" 282 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" 283 | integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== 284 | 285 | "@jridgewell/trace-mapping@^0.3.0": 286 | version "0.3.4" 287 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz#f6a0832dffd5b8a6aaa633b7d9f8e8e94c83a0c3" 288 | integrity sha512-vFv9ttIedivx0ux3QSjhgtCVjPZd5l46ZOMDSCwnH1yUO2e964gO8LZGyv2QkqcgR6TnBU1v+1IFqmeoG+0UJQ== 289 | dependencies: 290 | "@jridgewell/resolve-uri" "^3.0.3" 291 | "@jridgewell/sourcemap-codec" "^1.4.10" 292 | 293 | "@rollup/pluginutils@^4.1.2": 294 | version "4.1.2" 295 | resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.1.2.tgz#ed5821c15e5e05e32816f5fb9ec607cdf5a75751" 296 | integrity sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ== 297 | dependencies: 298 | estree-walker "^2.0.1" 299 | picomatch "^2.2.2" 300 | 301 | "@svgr/babel-plugin-add-jsx-attribute@^6.0.0": 302 | version "6.0.0" 303 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18" 304 | integrity sha512-MdPdhdWLtQsjd29Wa4pABdhWbaRMACdM1h31BY+c6FghTZqNGT7pEYdBoaGeKtdTOBC/XNFQaKVj+r/Ei2ryWA== 305 | 306 | "@svgr/babel-plugin-remove-jsx-attribute@^6.0.0": 307 | version "6.0.0" 308 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-6.0.0.tgz#58654908beebfa069681a83332544b17e5237e89" 309 | integrity sha512-aVdtfx9jlaaxc3unA6l+M9YRnKIZjOhQPthLKqmTXC8UVkBLDRGwPKo+r8n3VZN8B34+yVajzPTZ+ptTSuZZCw== 310 | 311 | "@svgr/babel-plugin-remove-jsx-empty-expression@^6.0.0": 312 | version "6.0.0" 313 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-6.0.0.tgz#d06dd6e8a8f603f92f9979bb9990a1f85a4f57ba" 314 | integrity sha512-Ccj42ApsePD451AZJJf1QzTD1B/BOU392URJTeXFxSK709i0KUsGtbwyiqsKu7vsYxpTM0IA5clAKDyf9RCZyA== 315 | 316 | "@svgr/babel-plugin-replace-jsx-attribute-value@^6.0.0": 317 | version "6.0.0" 318 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-6.0.0.tgz#0b85837577b02c31c09c758a12932820f5245cee" 319 | integrity sha512-88V26WGyt1Sfd1emBYmBJRWMmgarrExpKNVmI9vVozha4kqs6FzQJ/Kp5+EYli1apgX44518/0+t9+NU36lThQ== 320 | 321 | "@svgr/babel-plugin-svg-dynamic-title@^6.0.0": 322 | version "6.0.0" 323 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-6.0.0.tgz#28236ec26f7ab9d486a487d36ae52d58ba15676f" 324 | integrity sha512-F7YXNLfGze+xv0KMQxrl2vkNbI9kzT9oDK55/kUuymh1ACyXkMV+VZWX1zEhSTfEKh7VkHVZGmVtHg8eTZ6PRg== 325 | 326 | "@svgr/babel-plugin-svg-em-dimensions@^6.0.0": 327 | version "6.0.0" 328 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-6.0.0.tgz#40267c5dea1b43c4f83a0eb6169e08b43d8bafce" 329 | integrity sha512-+rghFXxdIqJNLQK08kwPBD3Z22/0b2tEZ9lKiL/yTfuyj1wW8HUXu4bo/XkogATIYuXSghVQOOCwURXzHGKyZA== 330 | 331 | "@svgr/babel-plugin-transform-react-native-svg@^6.0.0": 332 | version "6.0.0" 333 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-6.0.0.tgz#eb688d0a5f539e34d268d8a516e81f5d7fede7c9" 334 | integrity sha512-VaphyHZ+xIKv5v0K0HCzyfAaLhPGJXSk2HkpYfXIOKb7DjLBv0soHDxNv6X0vr2titsxE7klb++u7iOf7TSrFQ== 335 | 336 | "@svgr/babel-plugin-transform-svg-component@^6.2.0": 337 | version "6.2.0" 338 | resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-6.2.0.tgz#7ba61d9fc1fb42b0ba1a04e4630019fa7e993c4f" 339 | integrity sha512-bhYIpsORb++wpsp91fymbFkf09Z/YEKR0DnFjxvN+8JHeCUD2unnh18jIMKnDJTWtvpTaGYPXELVe4OOzFI0xg== 340 | 341 | "@svgr/babel-preset@^6.2.0": 342 | version "6.2.0" 343 | resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-6.2.0.tgz#1d3ad8c7664253a4be8e4a0f0e6872f30d8af627" 344 | integrity sha512-4WQNY0J71JIaL03DRn0vLiz87JXx0b9dYm2aA8XHlQJQoixMl4r/soYHm8dsaJZ3jWtkCiOYy48dp9izvXhDkQ== 345 | dependencies: 346 | "@svgr/babel-plugin-add-jsx-attribute" "^6.0.0" 347 | "@svgr/babel-plugin-remove-jsx-attribute" "^6.0.0" 348 | "@svgr/babel-plugin-remove-jsx-empty-expression" "^6.0.0" 349 | "@svgr/babel-plugin-replace-jsx-attribute-value" "^6.0.0" 350 | "@svgr/babel-plugin-svg-dynamic-title" "^6.0.0" 351 | "@svgr/babel-plugin-svg-em-dimensions" "^6.0.0" 352 | "@svgr/babel-plugin-transform-react-native-svg" "^6.0.0" 353 | "@svgr/babel-plugin-transform-svg-component" "^6.2.0" 354 | 355 | "@svgr/core@^6.2.0": 356 | version "6.2.1" 357 | resolved "https://registry.yarnpkg.com/@svgr/core/-/core-6.2.1.tgz#195de807a9f27f9e0e0d678e01084b05c54fdf61" 358 | integrity sha512-NWufjGI2WUyrg46mKuySfviEJ6IxHUOm/8a3Ph38VCWSp+83HBraCQrpEM3F3dB6LBs5x8OElS8h3C0oOJaJAA== 359 | dependencies: 360 | "@svgr/plugin-jsx" "^6.2.1" 361 | camelcase "^6.2.0" 362 | cosmiconfig "^7.0.1" 363 | 364 | "@svgr/hast-util-to-babel-ast@^6.2.1": 365 | version "6.2.1" 366 | resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-6.2.1.tgz#ae065567b74cbe745afae617053adf9a764bea25" 367 | integrity sha512-pt7MMkQFDlWJVy9ULJ1h+hZBDGFfSCwlBNW1HkLnVi7jUhyEXUaGYWi1x6bM2IXuAR9l265khBT4Av4lPmaNLQ== 368 | dependencies: 369 | "@babel/types" "^7.15.6" 370 | entities "^3.0.1" 371 | 372 | "@svgr/plugin-jsx@^6.2.1": 373 | version "6.2.1" 374 | resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-6.2.1.tgz#5668f1d2aa18c2f1bb7a1fc9f682d3f9aed263bd" 375 | integrity sha512-u+MpjTsLaKo6r3pHeeSVsh9hmGRag2L7VzApWIaS8imNguqoUwDq/u6U/NDmYs/KAsrmtBjOEaAAPbwNGXXp1g== 376 | dependencies: 377 | "@babel/core" "^7.15.5" 378 | "@svgr/babel-preset" "^6.2.0" 379 | "@svgr/hast-util-to-babel-ast" "^6.2.1" 380 | svg-parser "^2.0.2" 381 | 382 | "@types/events@^3.0.0": 383 | version "3.0.0" 384 | resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" 385 | integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== 386 | 387 | "@types/hoist-non-react-statics@*": 388 | version "3.3.1" 389 | resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" 390 | integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== 391 | dependencies: 392 | "@types/react" "*" 393 | hoist-non-react-statics "^3.3.0" 394 | 395 | "@types/parse-json@^4.0.0": 396 | version "4.0.0" 397 | resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" 398 | integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== 399 | 400 | "@types/prop-types@*": 401 | version "15.7.4" 402 | resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" 403 | integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== 404 | 405 | "@types/react-dom@^17.0.10": 406 | version "17.0.11" 407 | resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466" 408 | integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q== 409 | dependencies: 410 | "@types/react" "*" 411 | 412 | "@types/react@*", "@types/react@^17.0.33": 413 | version "17.0.39" 414 | resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce" 415 | integrity sha512-UVavlfAxDd/AgAacMa60Azl7ygyQNRwC/DsHZmKgNvPmRR5p70AJ5Q9EAmL2NWOJmeV+vVUI4IAP7GZrN8h8Ug== 416 | dependencies: 417 | "@types/prop-types" "*" 418 | "@types/scheduler" "*" 419 | csstype "^3.0.2" 420 | 421 | "@types/scheduler@*": 422 | version "0.16.2" 423 | resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" 424 | integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== 425 | 426 | "@types/styled-components@^5.1.23": 427 | version "5.1.23" 428 | resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.23.tgz#11e5740047f292b42a042c60c0ef16b58d5adef6" 429 | integrity sha512-zt8oQGU6XB4LH1Xpq169YnAVmt22+swzHJvyKMyTZu/z8+afvgKjjg0s79aAodgNSf36ZOEG6DyVAW/JhLH2Nw== 430 | dependencies: 431 | "@types/hoist-non-react-statics" "*" 432 | "@types/react" "*" 433 | csstype "^3.0.2" 434 | 435 | "@vitejs/plugin-react@^1.0.7": 436 | version "1.2.0" 437 | resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-1.2.0.tgz#4cfb4c0475e93885e56d66ff15e12ef4c34b0af0" 438 | integrity sha512-Rywwt0IXXg6yQ0hv3cMT3mtdDcGIw31mGaa+MMMAT651LhoXLF2yFy4LrakiTs7UKs7RPBo9eNgaS8pgl2A6Qw== 439 | dependencies: 440 | "@babel/core" "^7.16.12" 441 | "@babel/plugin-transform-react-jsx" "^7.16.7" 442 | "@babel/plugin-transform-react-jsx-development" "^7.16.7" 443 | "@babel/plugin-transform-react-jsx-self" "^7.16.7" 444 | "@babel/plugin-transform-react-jsx-source" "^7.16.7" 445 | "@rollup/pluginutils" "^4.1.2" 446 | react-refresh "^0.11.0" 447 | resolve "^1.22.0" 448 | 449 | ansi-styles@^3.2.1: 450 | version "3.2.1" 451 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 452 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 453 | dependencies: 454 | color-convert "^1.9.0" 455 | 456 | "babel-plugin-styled-components@>= 1.12.0": 457 | version "2.0.6" 458 | resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.6.tgz#6f76c7f7224b7af7edc24a4910351948c691fc90" 459 | integrity sha512-Sk+7o/oa2HfHv3Eh8sxoz75/fFvEdHsXV4grdeHufX0nauCmymlnN0rGhIvfpMQSJMvGutJ85gvCGea4iqmDpg== 460 | dependencies: 461 | "@babel/helper-annotate-as-pure" "^7.16.0" 462 | "@babel/helper-module-imports" "^7.16.0" 463 | babel-plugin-syntax-jsx "^6.18.0" 464 | lodash "^4.17.11" 465 | picomatch "^2.3.0" 466 | 467 | babel-plugin-syntax-jsx@^6.18.0: 468 | version "6.18.0" 469 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" 470 | integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= 471 | 472 | browserslist@^4.17.5: 473 | version "4.19.3" 474 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.3.tgz#29b7caad327ecf2859485f696f9604214bedd383" 475 | integrity sha512-XK3X4xtKJ+Txj8G5c30B4gsm71s69lqXlkYui4s6EkKxuv49qjYlY6oVd+IFJ73d4YymtM3+djvvt/R/iJwwDg== 476 | dependencies: 477 | caniuse-lite "^1.0.30001312" 478 | electron-to-chromium "^1.4.71" 479 | escalade "^3.1.1" 480 | node-releases "^2.0.2" 481 | picocolors "^1.0.0" 482 | 483 | callsites@^3.0.0: 484 | version "3.1.0" 485 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" 486 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== 487 | 488 | camelcase@^6.2.0: 489 | version "6.3.0" 490 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" 491 | integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== 492 | 493 | camelize@^1.0.0: 494 | version "1.0.0" 495 | resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" 496 | integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= 497 | 498 | caniuse-lite@^1.0.30001312: 499 | version "1.0.30001312" 500 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz#e11eba4b87e24d22697dae05455d5aea28550d5f" 501 | integrity sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ== 502 | 503 | chalk@^2.0.0: 504 | version "2.4.2" 505 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 506 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 507 | dependencies: 508 | ansi-styles "^3.2.1" 509 | escape-string-regexp "^1.0.5" 510 | supports-color "^5.3.0" 511 | 512 | color-convert@^1.9.0: 513 | version "1.9.3" 514 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 515 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 516 | dependencies: 517 | color-name "1.1.3" 518 | 519 | color-name@1.1.3: 520 | version "1.1.3" 521 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 522 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 523 | 524 | convert-source-map@^1.7.0: 525 | version "1.8.0" 526 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" 527 | integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== 528 | dependencies: 529 | safe-buffer "~5.1.1" 530 | 531 | cosmiconfig@^7.0.1: 532 | version "7.0.1" 533 | resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" 534 | integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== 535 | dependencies: 536 | "@types/parse-json" "^4.0.0" 537 | import-fresh "^3.2.1" 538 | parse-json "^5.0.0" 539 | path-type "^4.0.0" 540 | yaml "^1.10.0" 541 | 542 | css-color-keywords@^1.0.0: 543 | version "1.0.0" 544 | resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" 545 | integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU= 546 | 547 | css-to-react-native@^3.0.0: 548 | version "3.0.0" 549 | resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756" 550 | integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ== 551 | dependencies: 552 | camelize "^1.0.0" 553 | css-color-keywords "^1.0.0" 554 | postcss-value-parser "^4.0.2" 555 | 556 | csstype@^3.0.2: 557 | version "3.0.10" 558 | resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5" 559 | integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA== 560 | 561 | debug@^4.1.0: 562 | version "4.3.3" 563 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" 564 | integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== 565 | dependencies: 566 | ms "2.1.2" 567 | 568 | electron-to-chromium@^1.4.71: 569 | version "1.4.72" 570 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.72.tgz#19b871f1da8be8199b2330d694fc84fcdb72ecd9" 571 | integrity sha512-9LkRQwjW6/wnSfevR21a3k8sOJ+XWSH7kkzs9/EUenKmuDkndP3W9y1yCZpOxufwGbX3JV8glZZSDb4o95zwXQ== 572 | 573 | entities@^3.0.1: 574 | version "3.0.1" 575 | resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" 576 | integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== 577 | 578 | error-ex@^1.3.1: 579 | version "1.3.2" 580 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" 581 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== 582 | dependencies: 583 | is-arrayish "^0.2.1" 584 | 585 | esbuild-android-arm64@0.14.23: 586 | version "0.14.23" 587 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.23.tgz#c89b3c50b4f47668dcbeb0b34ee4615258818e71" 588 | integrity sha512-k9sXem++mINrZty1v4FVt6nC5BQCFG4K2geCIUUqHNlTdFnuvcqsY7prcKZLFhqVC1rbcJAr9VSUGFL/vD4vsw== 589 | 590 | esbuild-darwin-64@0.14.23: 591 | version "0.14.23" 592 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.23.tgz#1c131e8cb133ed935ca32f824349a117c896a15b" 593 | integrity sha512-lB0XRbtOYYL1tLcYw8BoBaYsFYiR48RPrA0KfA/7RFTr4MV7Bwy/J4+7nLsVnv9FGuQummM3uJ93J3ptaTqFug== 594 | 595 | esbuild-darwin-arm64@0.14.23: 596 | version "0.14.23" 597 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.23.tgz#3c6245a50109dd84953f53d7833bd3b4f0e8c6fa" 598 | integrity sha512-yat73Z/uJ5tRcfRiI4CCTv0FSnwErm3BJQeZAh+1tIP0TUNh6o+mXg338Zl5EKChD+YGp6PN+Dbhs7qa34RxSw== 599 | 600 | esbuild-freebsd-64@0.14.23: 601 | version "0.14.23" 602 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.23.tgz#0cdc54e72d3dd9cd992f9c2960055e68a7f8650c" 603 | integrity sha512-/1xiTjoLuQ+LlbfjJdKkX45qK/M7ARrbLmyf7x3JhyQGMjcxRYVR6Dw81uH3qlMHwT4cfLW4aEVBhP1aNV7VsA== 604 | 605 | esbuild-freebsd-arm64@0.14.23: 606 | version "0.14.23" 607 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.23.tgz#1d11faed3a0c429e99b7dddef84103eb509788b2" 608 | integrity sha512-uyPqBU/Zcp6yEAZS4LKj5jEE0q2s4HmlMBIPzbW6cTunZ8cyvjG6YWpIZXb1KK3KTJDe62ltCrk3VzmWHp+iLg== 609 | 610 | esbuild-linux-32@0.14.23: 611 | version "0.14.23" 612 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.23.tgz#fd9f033fc27dcab61100cb1eb1c936893a68c841" 613 | integrity sha512-37R/WMkQyUfNhbH7aJrr1uCjDVdnPeTHGeDhZPUNhfoHV0lQuZNCKuNnDvlH/u/nwIYZNdVvz1Igv5rY/zfrzQ== 614 | 615 | esbuild-linux-64@0.14.23: 616 | version "0.14.23" 617 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.23.tgz#c04c438514f1359ecb1529205d0c836d4165f198" 618 | integrity sha512-H0gztDP60qqr8zoFhAO64waoN5yBXkmYCElFklpd6LPoobtNGNnDe99xOQm28+fuD75YJ7GKHzp/MLCLhw2+vQ== 619 | 620 | esbuild-linux-arm64@0.14.23: 621 | version "0.14.23" 622 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.23.tgz#d1b3ab2988ab0734886eb9e811726f7db099ab96" 623 | integrity sha512-c4MLOIByNHR55n3KoYf9hYDfBRghMjOiHLaoYLhkQkIabb452RWi+HsNgB41sUpSlOAqfpqKPFNg7VrxL3UX9g== 624 | 625 | esbuild-linux-arm@0.14.23: 626 | version "0.14.23" 627 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.23.tgz#df7558b6a5076f5eb9fd387c8704f768b61d97fb" 628 | integrity sha512-x64CEUxi8+EzOAIpCUeuni0bZfzPw/65r8tC5cy5zOq9dY7ysOi5EVQHnzaxS+1NmV+/RVRpmrzGw1QgY2Xpmw== 629 | 630 | esbuild-linux-mips64le@0.14.23: 631 | version "0.14.23" 632 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.23.tgz#bb4c47fccc9493d460ffeb1f88e8a97a98a14f8b" 633 | integrity sha512-kHKyKRIAedYhKug2EJpyJxOUj3VYuamOVA1pY7EimoFPzaF3NeY7e4cFBAISC/Av0/tiV0xlFCt9q0HJ68IBIw== 634 | 635 | esbuild-linux-ppc64le@0.14.23: 636 | version "0.14.23" 637 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.23.tgz#a332dbc8a1b4e30cfe1261bfaa5cef57c9c8c02a" 638 | integrity sha512-7ilAiJEPuJJnJp/LiDO0oJm5ygbBPzhchJJh9HsHZzeqO+3PUzItXi+8PuicY08r0AaaOe25LA7sGJ0MzbfBag== 639 | 640 | esbuild-linux-riscv64@0.14.23: 641 | version "0.14.23" 642 | resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.23.tgz#85675f3f931f5cd7cfb238fd82f77a62ffcb6d86" 643 | integrity sha512-fbL3ggK2wY0D8I5raPIMPhpCvODFE+Bhb5QGtNP3r5aUsRR6TQV+ZBXIaw84iyvKC8vlXiA4fWLGhghAd/h/Zg== 644 | 645 | esbuild-linux-s390x@0.14.23: 646 | version "0.14.23" 647 | resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.23.tgz#a526282a696e6d846f4c628f5315475518c0c0f0" 648 | integrity sha512-GHMDCyfy7+FaNSO8RJ8KCFsnax8fLUsOrj9q5Gi2JmZMY0Zhp75keb5abTFCq2/Oy6KVcT0Dcbyo/bFb4rIFJA== 649 | 650 | esbuild-netbsd-64@0.14.23: 651 | version "0.14.23" 652 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.23.tgz#8e456605694719aa1be4be266d6cd569c06dfaf5" 653 | integrity sha512-ovk2EX+3rrO1M2lowJfgMb/JPN1VwVYrx0QPUyudxkxLYrWeBxDKQvc6ffO+kB4QlDyTfdtAURrVzu3JeNdA2g== 654 | 655 | esbuild-openbsd-64@0.14.23: 656 | version "0.14.23" 657 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.23.tgz#f2fc51714b4ddabc86e4eb30ca101dd325db2f7d" 658 | integrity sha512-uYYNqbVR+i7k8ojP/oIROAHO9lATLN7H2QeXKt2H310Fc8FJj4y3Wce6hx0VgnJ4k1JDrgbbiXM8rbEgQyg8KA== 659 | 660 | esbuild-sunos-64@0.14.23: 661 | version "0.14.23" 662 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.23.tgz#a408f33ea20e215909e20173a0fd78b1aaad1f8e" 663 | integrity sha512-hAzeBeET0+SbScknPzS2LBY6FVDpgE+CsHSpe6CEoR51PApdn2IB0SyJX7vGelXzlyrnorM4CAsRyb9Qev4h9g== 664 | 665 | esbuild-windows-32@0.14.23: 666 | version "0.14.23" 667 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.23.tgz#b9005bbff54dac3975ff355d5de2b5e37165d128" 668 | integrity sha512-Kttmi3JnohdaREbk6o9e25kieJR379TsEWF0l39PQVHXq3FR6sFKtVPgY8wk055o6IB+rllrzLnbqOw/UV60EA== 669 | 670 | esbuild-windows-64@0.14.23: 671 | version "0.14.23" 672 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.23.tgz#2b5a99befeaca6aefdad32d738b945730a60a060" 673 | integrity sha512-JtIT0t8ymkpl6YlmOl6zoSWL5cnCgyLaBdf/SiU/Eg3C13r0NbHZWNT/RDEMKK91Y6t79kTs3vyRcNZbfu5a8g== 674 | 675 | esbuild-windows-arm64@0.14.23: 676 | version "0.14.23" 677 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.23.tgz#edc560bbadb097eb45fc235aeacb942cb94a38c0" 678 | integrity sha512-cTFaQqT2+ik9e4hePvYtRZQ3pqOvKDVNarzql0VFIzhc0tru/ZgdLoXd6epLiKT+SzoSce6V9YJ+nn6RCn6SHw== 679 | 680 | esbuild@^0.14.14: 681 | version "0.14.23" 682 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.23.tgz#95e842cb22bc0c7d82c140adc16788aac91469fe" 683 | integrity sha512-XjnIcZ9KB6lfonCa+jRguXyRYcldmkyZ99ieDksqW/C8bnyEX299yA4QH2XcgijCgaddEZePPTgvx/2imsq7Ig== 684 | optionalDependencies: 685 | esbuild-android-arm64 "0.14.23" 686 | esbuild-darwin-64 "0.14.23" 687 | esbuild-darwin-arm64 "0.14.23" 688 | esbuild-freebsd-64 "0.14.23" 689 | esbuild-freebsd-arm64 "0.14.23" 690 | esbuild-linux-32 "0.14.23" 691 | esbuild-linux-64 "0.14.23" 692 | esbuild-linux-arm "0.14.23" 693 | esbuild-linux-arm64 "0.14.23" 694 | esbuild-linux-mips64le "0.14.23" 695 | esbuild-linux-ppc64le "0.14.23" 696 | esbuild-linux-riscv64 "0.14.23" 697 | esbuild-linux-s390x "0.14.23" 698 | esbuild-netbsd-64 "0.14.23" 699 | esbuild-openbsd-64 "0.14.23" 700 | esbuild-sunos-64 "0.14.23" 701 | esbuild-windows-32 "0.14.23" 702 | esbuild-windows-64 "0.14.23" 703 | esbuild-windows-arm64 "0.14.23" 704 | 705 | escalade@^3.1.1: 706 | version "3.1.1" 707 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 708 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 709 | 710 | escape-string-regexp@^1.0.5: 711 | version "1.0.5" 712 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 713 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 714 | 715 | estree-walker@^2.0.1: 716 | version "2.0.2" 717 | resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" 718 | integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== 719 | 720 | events@^3.3.0: 721 | version "3.3.0" 722 | resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 723 | integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== 724 | 725 | fsevents@~2.3.2: 726 | version "2.3.2" 727 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 728 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 729 | 730 | function-bind@^1.1.1: 731 | version "1.1.1" 732 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 733 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 734 | 735 | gensync@^1.0.0-beta.2: 736 | version "1.0.0-beta.2" 737 | resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" 738 | integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== 739 | 740 | globals@^11.1.0: 741 | version "11.12.0" 742 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 743 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 744 | 745 | has-flag@^3.0.0: 746 | version "3.0.0" 747 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 748 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 749 | 750 | has@^1.0.3: 751 | version "1.0.3" 752 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 753 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 754 | dependencies: 755 | function-bind "^1.1.1" 756 | 757 | history@^5.2.0: 758 | version "5.3.0" 759 | resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" 760 | integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== 761 | dependencies: 762 | "@babel/runtime" "^7.7.6" 763 | 764 | hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: 765 | version "3.3.2" 766 | resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" 767 | integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== 768 | dependencies: 769 | react-is "^16.7.0" 770 | 771 | import-fresh@^3.2.1: 772 | version "3.3.0" 773 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" 774 | integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== 775 | dependencies: 776 | parent-module "^1.0.0" 777 | resolve-from "^4.0.0" 778 | 779 | is-arrayish@^0.2.1: 780 | version "0.2.1" 781 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" 782 | integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= 783 | 784 | is-core-module@^2.8.1: 785 | version "2.8.1" 786 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" 787 | integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== 788 | dependencies: 789 | has "^1.0.3" 790 | 791 | "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: 792 | version "4.0.0" 793 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 794 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 795 | 796 | jsesc@^2.5.1: 797 | version "2.5.2" 798 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 799 | integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== 800 | 801 | json-parse-even-better-errors@^2.3.0: 802 | version "2.3.1" 803 | resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" 804 | integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== 805 | 806 | json5@^2.1.2: 807 | version "2.2.0" 808 | resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" 809 | integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== 810 | dependencies: 811 | minimist "^1.2.5" 812 | 813 | lines-and-columns@^1.1.6: 814 | version "1.2.4" 815 | resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" 816 | integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== 817 | 818 | lodash@^4.17.11: 819 | version "4.17.21" 820 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 821 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 822 | 823 | loose-envify@^1.1.0: 824 | version "1.4.0" 825 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" 826 | integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== 827 | dependencies: 828 | js-tokens "^3.0.0 || ^4.0.0" 829 | 830 | minimist@^1.2.5: 831 | version "1.2.5" 832 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 833 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 834 | 835 | ms@2.1.2: 836 | version "2.1.2" 837 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 838 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 839 | 840 | nanoid@^3.2.0: 841 | version "3.3.1" 842 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" 843 | integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== 844 | 845 | node-releases@^2.0.2: 846 | version "2.0.2" 847 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" 848 | integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== 849 | 850 | object-assign@^4.1.1: 851 | version "4.1.1" 852 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 853 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 854 | 855 | parent-module@^1.0.0: 856 | version "1.0.1" 857 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" 858 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== 859 | dependencies: 860 | callsites "^3.0.0" 861 | 862 | parse-json@^5.0.0: 863 | version "5.2.0" 864 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" 865 | integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== 866 | dependencies: 867 | "@babel/code-frame" "^7.0.0" 868 | error-ex "^1.3.1" 869 | json-parse-even-better-errors "^2.3.0" 870 | lines-and-columns "^1.1.6" 871 | 872 | path-parse@^1.0.7: 873 | version "1.0.7" 874 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 875 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 876 | 877 | path-type@^4.0.0: 878 | version "4.0.0" 879 | resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 880 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 881 | 882 | picocolors@^1.0.0: 883 | version "1.0.0" 884 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 885 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 886 | 887 | picomatch@^2.2.2, picomatch@^2.3.0: 888 | version "2.3.1" 889 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 890 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 891 | 892 | postcss-value-parser@^4.0.2: 893 | version "4.2.0" 894 | resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" 895 | integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== 896 | 897 | postcss@^8.4.6: 898 | version "8.4.6" 899 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1" 900 | integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== 901 | dependencies: 902 | nanoid "^3.2.0" 903 | picocolors "^1.0.0" 904 | source-map-js "^1.0.2" 905 | 906 | react-dom@^17.0.2: 907 | version "17.0.2" 908 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" 909 | integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== 910 | dependencies: 911 | loose-envify "^1.1.0" 912 | object-assign "^4.1.1" 913 | scheduler "^0.20.2" 914 | 915 | react-is@^16.7.0: 916 | version "16.13.1" 917 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" 918 | integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== 919 | 920 | react-refresh@^0.11.0: 921 | version "0.11.0" 922 | resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" 923 | integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== 924 | 925 | react-router-dom@^6.2.1: 926 | version "6.2.1" 927 | resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.1.tgz#32ec81829152fbb8a7b045bf593a22eadf019bec" 928 | integrity sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA== 929 | dependencies: 930 | history "^5.2.0" 931 | react-router "6.2.1" 932 | 933 | react-router@6.2.1, react-router@^6.2.1: 934 | version "6.2.1" 935 | resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.2.1.tgz#be2a97a6006ce1d9123c28934e604faef51448a3" 936 | integrity sha512-2fG0udBtxou9lXtK97eJeET2ki5//UWfQSl1rlJ7quwe6jrktK9FCCc8dQb5QY6jAv3jua8bBQRhhDOM/kVRsg== 937 | dependencies: 938 | history "^5.2.0" 939 | 940 | react@^17.0.2: 941 | version "17.0.2" 942 | resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" 943 | integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== 944 | dependencies: 945 | loose-envify "^1.1.0" 946 | object-assign "^4.1.1" 947 | 948 | regenerator-runtime@^0.13.4: 949 | version "0.13.9" 950 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" 951 | integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== 952 | 953 | resolve-from@^4.0.0: 954 | version "4.0.0" 955 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" 956 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== 957 | 958 | resolve@^1.22.0: 959 | version "1.22.0" 960 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" 961 | integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== 962 | dependencies: 963 | is-core-module "^2.8.1" 964 | path-parse "^1.0.7" 965 | supports-preserve-symlinks-flag "^1.0.0" 966 | 967 | rollup@^2.59.0: 968 | version "2.68.0" 969 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.68.0.tgz#6ccabfd649447f8f21d62bf41662e5caece3bd66" 970 | integrity sha512-XrMKOYK7oQcTio4wyTz466mucnd8LzkiZLozZ4Rz0zQD+HeX4nUK4B8GrTX/2EvN2/vBF/i2WnaXboPxo0JylA== 971 | optionalDependencies: 972 | fsevents "~2.3.2" 973 | 974 | safe-buffer@~5.1.1: 975 | version "5.1.2" 976 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 977 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 978 | 979 | scheduler@^0.20.2: 980 | version "0.20.2" 981 | resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" 982 | integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== 983 | dependencies: 984 | loose-envify "^1.1.0" 985 | object-assign "^4.1.1" 986 | 987 | semver@^6.3.0: 988 | version "6.3.0" 989 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" 990 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== 991 | 992 | shallowequal@^1.1.0: 993 | version "1.1.0" 994 | resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" 995 | integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== 996 | 997 | source-map-js@^1.0.2: 998 | version "1.0.2" 999 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 1000 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 1001 | 1002 | source-map@^0.5.0: 1003 | version "0.5.7" 1004 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1005 | integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= 1006 | 1007 | styled-components@^5.3.3: 1008 | version "5.3.3" 1009 | resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.3.tgz#312a3d9a549f4708f0fb0edc829eb34bde032743" 1010 | integrity sha512-++4iHwBM7ZN+x6DtPPWkCI4vdtwumQ+inA/DdAsqYd4SVgUKJie5vXyzotA00ttcFdQkCng7zc6grwlfIfw+lw== 1011 | dependencies: 1012 | "@babel/helper-module-imports" "^7.0.0" 1013 | "@babel/traverse" "^7.4.5" 1014 | "@emotion/is-prop-valid" "^0.8.8" 1015 | "@emotion/stylis" "^0.8.4" 1016 | "@emotion/unitless" "^0.7.4" 1017 | babel-plugin-styled-components ">= 1.12.0" 1018 | css-to-react-native "^3.0.0" 1019 | hoist-non-react-statics "^3.0.0" 1020 | shallowequal "^1.1.0" 1021 | supports-color "^5.5.0" 1022 | 1023 | supports-color@^5.3.0, supports-color@^5.5.0: 1024 | version "5.5.0" 1025 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1026 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1027 | dependencies: 1028 | has-flag "^3.0.0" 1029 | 1030 | supports-preserve-symlinks-flag@^1.0.0: 1031 | version "1.0.0" 1032 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 1033 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 1034 | 1035 | svg-parser@^2.0.2: 1036 | version "2.0.4" 1037 | resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" 1038 | integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== 1039 | 1040 | to-fast-properties@^2.0.0: 1041 | version "2.0.0" 1042 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 1043 | integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= 1044 | 1045 | typescript@^4.5.4: 1046 | version "4.5.5" 1047 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3" 1048 | integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA== 1049 | 1050 | vite-plugin-svgr@^1.0.1: 1051 | version "1.0.1" 1052 | resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-1.0.1.tgz#99bbbe474ba79dc7f00bdb508314012acf6f83b4" 1053 | integrity sha512-7jHIC2Bi83KRwBB/b5FLcQdqWfzOSDVy7BtE9g4GXoY1axTZRKeqyWMEK3LCySQ70+BSYFtoSwHJaNLeqxjYlg== 1054 | dependencies: 1055 | "@svgr/core" "^6.2.0" 1056 | 1057 | vite@^2.8.0: 1058 | version "2.8.4" 1059 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.8.4.tgz#4e52a534289b7b4e94e646df2fc5556ceaa7336b" 1060 | integrity sha512-GwtOkkaT2LDI82uWZKcrpRQxP5tymLnC7hVHHqNkhFNknYr0hJUlDLfhVRgngJvAy3RwypkDCWtTKn1BjO96Dw== 1061 | dependencies: 1062 | esbuild "^0.14.14" 1063 | postcss "^8.4.6" 1064 | resolve "^1.22.0" 1065 | rollup "^2.59.0" 1066 | optionalDependencies: 1067 | fsevents "~2.3.2" 1068 | 1069 | yaml@^1.10.0: 1070 | version "1.10.2" 1071 | resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" 1072 | integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== 1073 | 1074 | zustand@^3.7.0: 1075 | version "3.7.0" 1076 | resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.0.tgz#a5c68fb06bdee9c63ad829de2432635be6d0ce69" 1077 | integrity sha512-USzVzLGrvZ8VK1/sEsOAmeqa8N7D3OBdZskVaL7DL89Q4QLTYD053iIlZ5KDidyZ+Od80Dttin/f8ZulOLFFDQ== 1078 | --------------------------------------------------------------------------------