├── .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 |
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 |
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 |
44 |
53 |
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 |
94 | Wake up
95 |
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 |
57 |
58 | Type
59 |
62 | update({ type: parseInt(e.currentTarget.value) })
63 | }
64 | >
65 | PC
66 | Server
67 | IoT Device
68 | Mobile Device
69 |
70 |
71 |
79 |
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 |
--------------------------------------------------------------------------------