console: switch to Vue.JS + Tailwind CSS + Headless UI for the frontend

This commit is contained in:
Vincent Bernat
2022-04-03 22:49:24 +02:00
parent cb541fd6e9
commit ce7fce32ba
42 changed files with 1502 additions and 489 deletions

4
.gitignore vendored
View File

@@ -2,5 +2,5 @@
/test/
/inlet/flow/decoder/flow*.pb.go
/console/data/node_modules/
/console/data/assets/generated/
/console/frontend/node_modules/
/console/data/frontend/

View File

@@ -14,6 +14,11 @@ run tests:
paths:
- .go-cache/
- bin/
- key:
files:
- yarn.lock
paths:
- .yarn-cache
variables:
FF_NETWORK_PER_BUILD: "true"
CI_AKVORADO_FUNCTIONAL_TESTS: "true"
@@ -31,8 +36,9 @@ run tests:
- name: clickhouse/clickhouse-server:22.2
alias: clickhouse
script:
- export GOMODCACHE=$PWD/.go-cache
- time apk add --no-cache git make gcc musl-dev protoc shared-mime-info yarn
- export GOMODCACHE=$PWD/.go-cache
- yarn config set cacheFolder $PWD/.yarn-cache
- time go mod download
- time make test
- time make test-race || make test-race

View File

@@ -13,7 +13,7 @@ M = $(shell if [ "$$(tput colors 2> /dev/null || echo 0)" -ge 8 ]; then printf "
export GO111MODULE=on
GENERATED = inlet/flow/decoder/flow-1.pb.go console/data/node_modules console/data/assets/generated
GENERATED = inlet/flow/decoder/flow-1.pb.go console/data/frontend console/frontend/node_modules
.PHONY: all
all: fmt lint $(GENERATED) | $(BIN) ; $(info $(M) building executable) @ ## Build program binary
@@ -50,14 +50,14 @@ $(BIN)/protoc-gen-go: PACKAGE=google.golang.org/protobuf/cmd/protoc-gen-go
inlet/flow/decoder/%.pb.go: inlet/flow/data/schemas/%.proto | $(PROTOC_GEN_GO) ; $(info $(M) compiling protocol buffers definition)
$Q $(PROTOC) -I=. --plugin=$(PROTOC_GEN_GO) --go_out=. --go_opt=module=$(MODULE) $<
console/data/node_modules: console/data/package.json console/data/yarn.lock ; $(info $(M) fetching node modules)
$Q yarn install --frozen-lockfile --cwd console/data && touch $@
console/data/assets/generated: console/data/node_modules Makefile ; $(info $(M) copying static assets)
$Q rm -rf $@ && mkdir -p $@/stylesheets $@/javascript $@/fonts
$Q cp console/data/node_modules/@mdi/font/fonts/materialdesignicons-webfont.woff* $@/fonts/.
$Q cp console/data/node_modules/@mdi/font/css/materialdesignicons.min.css $@/stylesheets/.
$Q cp console/data/node_modules/bootstrap/dist/css/bootstrap.min.css $@/stylesheets/.
$Q cp console/data/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js $@/javascript/.
console/frontend/node_modules: console/frontend/package.json console/frontend/yarn.lock
console/frontend/node_modules: ; $(info $(M) fetching node modules)
$Q yarn install --frozen-lockfile --cwd console/frontend && touch $@
console/data/frontend: Makefile console/frontend/node_modules
console/data/frontend: console/frontend/index.html console/frontend/vite.config.js
console/data/frontend: $(shell find console/frontend/src -type f)
console/data/frontend: ; $(info $(M) building console frontend)
$Q cd console/frontend && yarn build
# These files are versioned in Git, but we may want to update them.
clickhouse/data/protocols.csv:

View File

@@ -76,8 +76,7 @@ func consoleStart(r *reporter.Reporter, config ConsoleConfiguration, checkOnly b
return fmt.Errorf("unable to initialize HTTP component: %w", err)
}
consoleComponent, err := console.New(r, config.Console, console.Dependencies{
Daemon: daemonComponent,
HTTP: httpComponent,
HTTP: httpComponent,
})
if err != nil {
return fmt.Errorf("unable to initialize console component: %w", err)

View File

@@ -2,38 +2,32 @@ package console
import (
"embed"
"errors"
"fmt"
"io/fs"
"net/http"
"path"
"strings"
"time"
)
//go:embed data/assets/generated data/assets/images
//go:embed data/frontend
var embeddedAssets embed.FS
func (c *Component) assetsHandlerFunc(w http.ResponseWriter, req *http.Request) {
assets := c.embedOrLiveFS(embeddedAssets, "data/assets")
rpath := strings.TrimPrefix(req.URL.Path, "/assets/")
rpath = strings.Trim(rpath, "/")
for _, p := range []string{
fmt.Sprintf("%s", rpath),
fmt.Sprintf("generated/%s", rpath),
} {
f, err := http.FS(assets).Open(p)
if errors.Is(err, fs.ErrNotExist) {
continue
}
st, err := f.Stat()
if err != nil || st.IsDir() {
continue
}
http.ServeContent(w, req, path.Base(rpath), st.ModTime(), f)
f.Close()
assets := c.embedOrLiveFS(embeddedAssets, "data/frontend")
upath := req.URL.Path
if !strings.HasPrefix(upath, "/") {
upath = "/" + upath
req.URL.Path = upath
}
// Serve assets using a file server
if strings.HasPrefix(upath, "/assets/") {
http.FileServer(http.FS(assets)).ServeHTTP(w, req)
return
}
http.Error(w, "Asset not found.", http.StatusNotFound)
// Everything else is routed to index.html
f, err := http.FS(assets).Open("index.html")
if err != nil {
http.Error(w, "Application not found.", http.StatusInternalServerError)
}
http.ServeContent(w, req, "index.html", time.Time{}, f)
f.Close()
}

View File

@@ -5,20 +5,28 @@ import (
netHTTP "net/http"
"testing"
"akvorado/common/daemon"
"akvorado/common/http"
"akvorado/common/reporter"
)
func TestServeAssets(t *testing.T) {
for _, live := range []bool{false, true} {
for _, f := range []string{"images/akvorado.svg", "javascript/bootstrap.bundle.min.js"} {
cases := []struct {
Path string
Code int
}{
{"", 200},
{"something", 200},
{"assets/akvorado.399701ee.svg", 200},
{"assets/somethingelse.svg", 404},
}
for _, tc := range cases {
var name string
switch live {
case true:
name = fmt.Sprintf("livefs-%s", f)
name = fmt.Sprintf("livefs-%s", tc.Path)
case false:
name = fmt.Sprintf("embeddedfs-%s", f)
name = fmt.Sprintf("embeddedfs-%s", tc.Path)
}
t.Run(name, func(t *testing.T) {
r := reporter.NewMock(t)
@@ -26,20 +34,19 @@ func TestServeAssets(t *testing.T) {
_, err := New(r, Configuration{
ServeLiveFS: live,
}, Dependencies{
HTTP: h,
Daemon: daemon.NewMock(t),
HTTP: h,
})
if err != nil {
t.Fatalf("New() error:\n%+v", err)
}
resp, err := netHTTP.Get(fmt.Sprintf("http://%s/assets/%s", h.Address, f))
resp, err := netHTTP.Get(fmt.Sprintf("http://%s/%s", h.Address, tc.Path))
if err != nil {
t.Fatalf("GET /assets/%s:\n%+v", f, err)
t.Fatalf("GET /%s:\n%+v", tc.Path, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("GET /assets/%s: got status code %d, not 200", f, resp.StatusCode)
if resp.StatusCode != tc.Code {
t.Errorf("GET /%s: got status code %d, not %d", tc.Path, resp.StatusCode, tc.Code)
}
})
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 553 KiB

View File

@@ -1,10 +0,0 @@
:root {
--bs-primary: #008aff;
--bs-primary-rgb: 0,138,255;
--bs-secondary: #48092f;
--bs-secondary-rgb: 72,9,47;
}
.ak-navbar {
background-color: #e3f2fd;
}

View File

@@ -1,10 +0,0 @@
.ak-docs-markdown p > img {
margin: 0 auto;
display: block;
max-width: 100%;
}
.ak-docs-toc ul {
padding-left: 0;
list-style: none;
}

View File

@@ -1,5 +1,3 @@
![Akvorado logo](../assets/images/akvorado.svg)
# Introduction
*Akvorado*[^name] is a flow collector, hydrater and exporter. It
@@ -12,7 +10,7 @@ exports them to Kafka.
## Big picture
![General design](../assets/images/design.svg)
![General design](design.svg)
*Akvorado* is split into three components:

View File

@@ -175,3 +175,16 @@ FROM flows
If the lag is too big, you need to increase the number of threads. See
[ClickHouse configuration](02-configuration.md#clickhouse) for details.
Another way to achieve the same thing is to look at the consumer group
from Kafka's point of view:
```console
$ kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group clickhouse
GROUP TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID
clickhouse flows-v1 0 5650351527 5650374314 22787 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-0-77740d0a-79b7-4bef-a501-25a819c3cee4 /240.0.4.8 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-0
clickhouse flows-v1 3 3035602619 3035628290 25671 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-3-1e4629b0-69a3-48dd-899a-20f4b16be0a2 /240.0.4.8 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-3
clickhouse flows-v1 2 1645914467 1645930257 15790 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-2-79c9bafe-fd36-42fe-921f-a802d46db684 /240.0.4.8 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-2
clickhouse flows-v1 1 889117276 889129896 12620 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-1-f0421bbe-ba13-49df-998f-83e49045be00 /240.0.4.8 ClickHouse-ee97b7e7e5e0-default-flows_1_raw-1
```

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 554 KiB

View File

@@ -1,6 +0,0 @@
{
"dependencies": {
"@mdi/font": "6.x",
"bootstrap": "^5.1.3"
}
}

View File

@@ -1,41 +0,0 @@
{{ define "title" }}Documentation{{ end }}
{{ define "stylesheets" }}
<link href="{{ .RootPath }}/assets/stylesheets/docs.css" rel="stylesheet">
{{ end }}
{{ define "content" }}
<div class="container-xxl">
<div class="row">
<div class="mb-3 col-12 col-md-3 col-lg-2">
<nav class="shadow-sm bg-light py-2 px-3 ak-docs-toc">
<strong>Documentation</strong>
<ul>
{{ range .TOC }}
<li><a href="{{ .Name }}">{{ (index .Headers 0).Title }}</a></li>
{{ end }}
</ul>
</nav>
</div>
<div class="col-12 col-md-9 col-lg-8 px-1 px-md-5 ak-docs-markdown">
{{ .Markdown }}
</div>
<div class="col-2 d-none d-lg-block">
<nav class="shadow-sm bg-light py-2 px-3 ak-docs-toc">
<strong>Table of content</strong>
<ul>
{{ $root := . }}
{{ range .TOC }}
{{ if eq .Name ($root.CurrentPath | trimPrefix "/docs/") }}
{{ range .Headers }}
{{ if ge .Level 2 }}
<li><a class="ms-{{ sub .Level 2 }} d-inline-block"
href="#{{ .ID }}">{{ .Title }}</a></li>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</ul>
</nav>
</div>
</div>
</div>
{{ end }}

View File

@@ -1,4 +0,0 @@
{{ define "title" }}Dummy test{{ end }}
{{ define "content" }}
Hello there!
{{ end }}

View File

@@ -1,24 +0,0 @@
{{ define "base" }}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<link href="{{ .RootPath }}/assets/stylesheets/bootstrap.min.css" rel="stylesheet">
<link href="{{ .RootPath }}/assets/stylesheets/materialdesignicons.min.css" rel="stylesheet">
<link href="{{ .RootPath }}/assets/stylesheets/akvorado.css" rel="stylesheet">
{{ block "stylesheets" . }}{{ end }}
<link href="{{ .RootPath }}/assets/images/akvorado.svg" rel="icon">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{{ template "title" . }}</title>
</head>
<body>
{{ template "navigation.html" . }}
<main class="container-fluid pt-4">
{{ template "content" . }}
</main>
</body>
<script src="{{ .RootPath }}/assets/javascript/bootstrap.bundle.min.js"></script>
{{ block "scripts" . }}{{ end }}
</script>
</html>
{{ end }}

View File

@@ -1,29 +0,0 @@
<header class="navbar navbar-expand-sm navbar-light py-1 shadow-sm ak-navbar">
<nav class="container-xxl flex-wrap flex-sm-nowrap" aria-="Main navigation">
<a class="navbar-brand p-0 me-2"
href="{{ .RootPath }}"
aria-label="Akvorado">
<img alt="Akvorado logo"
height="40"
src="{{ .RootPath }}/assets/images/akvorado.svg">
Akvorado
</a>
<button class="navbar-toggler collapsed" type="button"
data-bs-toggle="collapse" data-bs-target="#akNavbar"
aria-controls="akNavbar" aria-expanded="false" aria-label="Toggle navigation">
<i class="mdi mdi-menu"></i>
</button>
<div class="navbar-collapse collapse" id="akNavbar">
<ul class="navbar-nav flex-row flex-wrap pt-2 py-sm-0 ms-sm-auto">
<li class="navitem col-6 col-sm-auto">
<a class="nav-link pt-2{{ if eq .CurrentPath "/" }} active{{ end }}"
href="{{ .RootPath }}"><i class="mdi mdi-home"> </i>Home</a>
</li>
<li class="navitem col-6 col-sm-auto">
<a class="nav-link pt-2{{ if .CurrentPath | hasPrefix "/docs" }} active{{ end }}"
href="{{ .RootPath }}/docs/intro"><i class="mdi mdi-book-open-variant"> </i>Docs</a>
</li>
</ul>
</div>
</nav>
</header>

View File

@@ -1,13 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@mdi/font@6.x":
version "6.6.96"
resolved "https://registry.yarnpkg.com/@mdi/font/-/font-6.6.96.tgz#4eee6faee5f44d3ec401d354fb95775cd6699575"
integrity sha512-FbcvG9z17hwZ7IwX5XeOR1UYGoLq+gTKq6XNPvJFuCpn599GdiPCJbAmmDBJb+jMYXjKYr0lCxfouWGxDA82sA==
bootstrap@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.1.3.tgz#ba081b0c130f810fa70900acbc1c6d3c28fa8f34"
integrity sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==

View File

@@ -3,8 +3,10 @@ package console
import (
"bytes"
"embed"
"encoding/base64"
"encoding/json"
"fmt"
"html/template"
"io"
"io/fs"
"io/ioutil"
"net/http"
@@ -16,6 +18,7 @@ import (
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/extension"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
@@ -28,26 +31,20 @@ var (
// Header describes a document header.
type Header struct {
Level int
ID string
Title string
Level int `json:"level"`
ID string `json:"id"`
Title string `json:"title"`
}
// DocumentTOC describes the TOC of a document
type DocumentTOC struct {
Name string
Headers []Header
}
type templateDocData struct {
templateBaseData
Markdown template.HTML
TOC []DocumentTOC
Name string `json:"name"`
Headers []Header `json:"headers"`
}
func (c *Component) docsHandlerFunc(w http.ResponseWriter, req *http.Request) {
docs := c.embedOrLiveFS(embeddedDocs, "data/docs")
rpath := strings.TrimPrefix(req.URL.Path, "/docs/")
rpath := strings.TrimPrefix(req.URL.Path, "/api/v0/docs/")
rpath = strings.Trim(rpath, "/")
var markdown []byte
@@ -107,15 +104,21 @@ func (c *Component) docsHandlerFunc(w http.ResponseWriter, req *http.Request) {
return
}
md := goldmark.New(
goldmark.WithRendererOptions(
html.WithUnsafe(),
),
goldmark.WithExtensions(
extension.Footnote,
extension.Typographer,
highlighting.Highlighting,
highlighting.NewHighlighting(
highlighting.WithStyle("dracula"),
),
),
goldmark.WithParserOptions(
parser.WithAutoHeadingID(),
parser.WithASTTransformers(
util.Prioritized(&internalLinkTransformer{}, 500),
util.Prioritized(&imageEmbedder{docs}, 500),
),
),
)
@@ -126,13 +129,13 @@ func (c *Component) docsHandlerFunc(w http.ResponseWriter, req *http.Request) {
return
}
w.Header().Set("Cache-Control", "max-age=300")
c.renderTemplate(w, "docs.html", templateDocData{
templateBaseData: templateBaseData{
RootPath: "..",
CurrentPath: req.URL.Path,
},
Markdown: template.HTML(buf.String()),
TOC: toc,
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
encoder.SetEscapeHTML(false)
encoder.Encode(map[string]interface{}{
"markdown": buf.String(),
"toc": toc,
})
}
@@ -155,6 +158,37 @@ func (r *internalLinkTransformer) Transform(node *ast.Document, reader text.Read
ast.Walk(node, replaceLinks)
}
type imageEmbedder struct {
root fs.FS
}
func (r *imageEmbedder) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
replaceLinks := func(n ast.Node, entering bool) (ast.WalkStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
switch node := n.(type) {
case *ast.Image:
path := string(node.Destination)
if strings.Index(path, "/") != -1 || !strings.HasSuffix(path, ".svg") {
break
}
f, err := r.root.Open(path)
if err != nil {
break
}
content, err := io.ReadAll(f)
if err != nil {
break
}
encoded := fmt.Sprintf("data:image/svg+xml;base64,%s", base64.StdEncoding.EncodeToString(content))
node.Destination = []byte(encoded)
}
return ast.WalkContinue, nil
}
ast.Walk(node, replaceLinks)
}
type tocLogger struct {
headers []Header
}

View File

@@ -7,7 +7,6 @@ import (
"strings"
"testing"
"akvorado/common/daemon"
"akvorado/common/http"
"akvorado/common/reporter"
)
@@ -18,31 +17,42 @@ func TestServeDocs(t *testing.T) {
if !live {
name = "embeddedfs"
}
t.Run(name, func(t *testing.T) {
r := reporter.NewMock(t)
h := http.NewMock(t, r)
_, err := New(r, Configuration{
ServeLiveFS: live,
}, Dependencies{
HTTP: h,
Daemon: daemon.NewMock(t),
})
if err != nil {
t.Fatalf("New() error:\n%+v", err)
}
cases := []struct {
Path string
Expect string
}{
{"usage", `<a href=\"configuration\">configuration section</a>`},
{"intro", `data:image/svg`},
}
for _, tc := range cases {
t.Run(fmt.Sprintf("%s-%s", name, tc.Path), func(t *testing.T) {
r := reporter.NewMock(t)
h := http.NewMock(t, r)
_, err := New(r, Configuration{
ServeLiveFS: live,
}, Dependencies{
HTTP: h,
})
if err != nil {
t.Fatalf("New() error:\n%+v", err)
}
resp, err := netHTTP.Get(fmt.Sprintf("http://%s/docs/usage", h.Address))
if err != nil {
t.Fatalf("GET /docs/usage:\n%+v", err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("GET /docs/usage: got status code %d, not 200", resp.StatusCode)
}
body, _ := ioutil.ReadAll(resp.Body)
if strings.Contains(string(body), "configuration.md") {
t.Errorf("GET /docs/usage: contains %q while it should not", "configuration.md")
}
})
resp, err := netHTTP.Get(fmt.Sprintf("http://%s/api/v0/docs/%s",
h.Address, tc.Path))
if err != nil {
t.Fatalf("GET /api/v0/docs/%s:\n%+v", tc.Path, err)
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
t.Errorf("GET /api/v0/docs/%s: got status code %d, not 200",
tc.Path, resp.StatusCode)
}
body, _ := ioutil.ReadAll(resp.Body)
if !strings.Contains(string(body), tc.Expect) {
t.Errorf("GET /api/v0/docs/%s: does not contain %q",
tc.Path, tc.Expect)
}
})
}
}
}

24
console/frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en" class="h-screen scroll-pt-60">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/src/assets/images/akvorado.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Akvorado</title>
</head>
<body class="bg-white text-gray-900 dark:bg-gray-800 dark:text-gray-100">
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -0,0 +1,25 @@
{
"name": "akvorado",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@headlessui/vue": "^1.5.0",
"@heroicons/vue": "^1.0.6",
"notiwind": "^1.2.5",
"vue": "^3.2.25",
"vue-router": "^4.0.14"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.2",
"@vitejs/plugin-vue": "^2.3.0",
"autoprefixer": "^10.4.4",
"postcss": "^8.4.12",
"tailwindcss": "^3.0.23",
"vite": "^2.9.0"
}
}

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -0,0 +1,14 @@
<template>
<Notifications />
<Navigation />
<main>
<router-view />
</main>
</template>
<script setup>
import './tailwind.css';
import Navigation from "./components/Navigation.vue";
import Notifications from "./components/Notifications.vue";
</script>

View File

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -0,0 +1,33 @@
<template>
<button type="button"
@click="toggle()"
class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5">
<MoonIcon class="w-5 h-5" v-if="!dark" />
<SunIcon class="w-5 h-5" v-if="dark" />
</button>
</template>
<script setup>
import { ref, watch } from 'vue';
import { SunIcon, MoonIcon } from '@heroicons/vue/solid';
const dark = ref(false);
if (localStorage.getItem('color-theme') === 'dark' ||
(!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
dark.value = true;
}
watch(dark, (dark) => {
if (dark) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, { immediate: true });
const toggle = () => {
dark.value = !dark.value;
localStorage.setItem('color-theme', dark.value?'dark':'light');
}
</script>

View File

@@ -0,0 +1,48 @@
<template>
<Disclosure as="nav"
v-slot="{ open }"
class="w-full relative z-40 bg-gradient-to-r from-blue-100 to-indigo-200 border-gray-200 px-2 sm:px-4 py-2.5 dark:from-gray-800 dark:to-gray-600 shadow">
<div class="container flex flex-wrap justify-between items-center mx-auto">
<router-link to="/" class="flex items-center">
<img src="../assets/images/akvorado.svg" class="mr-3 h-6 sm:h-9" alt="Akvorado Logo" />
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">Akvorado</span>
</router-link>
<div class="flex md:order-2">
<DarkMode />
<DisclosureButton class="inline-flex items-center p-2 ml-3 text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600">
<span class="sr-only">Open main menu</span>
<MenuIcon v-if="!open" class="w-6 h-6" />
<XIcon v-else class="w-6 h-6" />
</DisclosureButton>
</div>
<DisclosurePanel static class="justify-between items-center w-full md:block md:w-auto md:order-1"
:class="open?'block':'hidden'">
<ul class="flex flex-col mt-4 md:flex-row md:space-x-8 md:mt-0 md:text-sm md:font-medium">
<li v-for="item in navigation" :key="item.name">
<router-link class="block py-2 pr-4 pl-3 rounded"
:class="item.current?['text-white bg-blue-700 md:bg-transparent md:text-blue-700 dark:text-white']:['text-gray-700 hover:bg-gray-50 md:hover:text-blue-700 dark:text-gray-400 md:dark:hover:text-white dark:hover:bg-gray-700 dark:hover:text-white dark:border-gray-700']"
:to="item.link"
:aria-current="item.current && 'page'">
<component :is="item.icon" class="w-5 h-5 inline"></component>
{{ item.name }}
</router-link>
</li>
</ul>
</DisclosurePanel>
</div>
</Disclosure>
</template>
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { Disclosure, DisclosureButton, DisclosurePanel } from '@headlessui/vue';
import { HomeIcon, BookOpenIcon, MenuIcon, XIcon } from '@heroicons/vue/solid';
import DarkMode from './DarkMode.vue';
const route = useRoute();
const navigation = computed(() => [
{ name: 'Home', icon: HomeIcon, link: '/', current: route.path == '/' },
{ name: 'Documentation', icon: BookOpenIcon, link: '/docs', current: route.path.startsWith('/docs') },
]);
</script>

View File

@@ -0,0 +1,46 @@
<template>
<NotificationGroup group="top">
<div class="fixed z-50 inset-0 flex items-start justify-center p-6 px-4 py-6 pointer-events-none">
<div class="w-full max-w-sm">
<Notification
v-slot="{ notifications, close }"
enter="transform ease-out duration-300 transition"
enter-from="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-4"
enter-to="translate-y-0 opacity-100 sm:translate-x-0"
leave="transition ease-in duration-500"
leave-from="opacity-100"
leave-to="opacity-0"
move="transition duration-500"
move-delay="delay-300">
<div
class="flex w-full max-w-sm mx-auto mt-4 overflow-hidden bg-stone-100 dark:bg-stone-800 rounded-lg shadow-md pointer-events-auto"
v-for="notification in notifications"
:key="notification.id">
<div
class="flex w-6"
:class="{'bg-red-500': notification.kind === 'error',
'bg-green-500': notification.kind === 'success',
'bg-blue-500': notification.kind === 'info'}">&nbsp;
</div>
<div class="ml-3 w-0 flex-1 py-4">
<p class="font-semibold text-gray-800 dark:text-gray-300">{{ notification.title }}</p>
<p class="text-sm">{{ notification.text }}</p>
</div>
<div class="flex flex-shrink-0 ml-4 p-2 h-full">
<button
@click="close(notification.id)"
class="inline-flex text-gray-400 rounded-md hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:focus:ring-gray-600">
<span class="sr-only">Close</span>
<XIcon class="w-5 h-5" />
</button>
</div>
</div>
</Notification>
</div>
</div>
</NotificationGroup>
</template>
<script setup>
import { XIcon } from '@heroicons/vue/solid';
</script>

View File

@@ -0,0 +1,9 @@
import { createApp } from 'vue';
import Notifications from 'notiwind'
import App from './App.vue';
import router from "./router";
createApp(App)
.use(router)
.use(Notifications)
.mount('#app');

View File

@@ -0,0 +1,16 @@
import {createRouter, createWebHistory} from 'vue-router';
import Home from '../views/Home.vue';
import Doc from '../views/Doc.vue';
import NotFound from '../views/NotFound.vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/", name: "Home", component: Home },
{ path: "/docs", redirect: "/docs/intro" },
{ path: "/docs/:id", name: "Documentation", component: Doc },
{ path: "/:pathMatch(.*)", component: NotFound }
],
});
export default router;

View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,86 @@
<template>
<div class="w-full flex-none md:grid md:grid-cols-3 md:gap-8">
<div class="fixed top-0 h-full overflow-y-auto bg-gray-50 dark:bg-gray-900 dark:text-gray-200 md:mx-0 py-20 px-4 sm:px-6 md:pl-0 md:pr-8">
<nav class="text-sm max-w-[37.5rem] mx-auto md:max-w-none md:mx-0 relative">
<ul class="space-y-8 pl-6">
<li v-for="document in toc" :key="document.name" class="space-y-3">
<router-link :to="{path: document.name, hash: `#${document.headers[0].id}` }"
class="block font-semibold"
:class="{ 'text-blue-600': activeDocument === document.name,
'dark:text-blue-300': activeDocument === document.name,
'text-gray-900': activeDocument !== document.name,
'dark:text-gray-300': activeDocument !== document.name }">
{{ document.headers[0].title }}
</router-link>
<ul class="space-y-3">
<template v-for="header in document.headers">
<li v-if="header.level >= 2 && header.level <= 3"
:class="{'ml-2': (header.level == 2), 'ml-4': (header.level == 3)}">
<router-link
:to="{path: document.name, hash: `#${header.id}` }" class="block"
:class="{ 'text-blue-600': activeDocument === document.name && activeSlug === header.id,
'dark:text-blue-300': activeDocument === document.name && activeSlug === header.id }">
{{ header.title }}
</router-link>
</li>
</template>
</ul>
</li>
</ul>
</nav>
</div>
<div class="col-span-2 md:-ml-8 md:shadow-md">
<div class="py-4 px-4 md:px-16">
<div class="prose prose-sm md:prose-base max-w-[37.5rem] mx-auto dark:prose-invert prose-h1:border-b-2 dark:prose-h1:border-gray-700 prose-img:center prose-pre:rounded"
v-html="markdown">
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import { notify } from "notiwind";
const route = useRoute();
const markdown = ref('');
const toc = ref([]);
const activeDocument = ref(null);
const activeSlug = computed(() => route.hash.replace(/^#/, ''));
watch(() => ({ id: route.params.id, hash: route.hash }),
async (to, from) => {
if (to.id !== from?.id) {
const id = to.id;
try {
const response = await fetch(`/api/v0/docs/${id}`);
if (!response.ok) {
throw `got a ${response.status} error`;
}
const data = await response.json();
markdown.value = data.markdown;
toc.value = data.toc;
activeDocument.value = id;
} catch (error) {
console.error(`while retrieving ${id}:`, error);
markdown.value = "hello";
markdown.toc = [];
activeDocument.value = "nothing";
}
}
if (to.id !== from?.id || to.hash !== from?.hash) {
if (to.hash === "") {
window.scrollTo(0, 0);
} else {
await nextTick();
const el = document.querySelector(to.hash);
if (el !== null) {
window.scrollTo(0, el.getBoundingClientRect().top + window.pageYOffset);
}
}
}
}, { immediate: true });
</script>

View File

@@ -0,0 +1,3 @@
<template>
<img class="mt-10 mx-auto" src="../assets/images/akvorado.svg">
</template>

View File

@@ -0,0 +1,4 @@
<template>
<img class="mt-10 mx-auto" src="../assets/images/akvorado.svg">
<h1 class="mt-10 text-center font-bold text-5xl">Not found!</h1>
</template>

View File

@@ -0,0 +1,13 @@
module.exports = {
content: [
'./index.html',
'./src/**/*.{vue,js,ts,jsx,tsx}',
],
darkMode: 'class',
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/typography'),
],
}

View File

@@ -0,0 +1,19 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
build: {
outDir: "../data/frontend",
emptyOutDir: true,
},
server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
},
},
},
})

973
console/frontend/yarn.lock Normal file
View File

@@ -0,0 +1,973 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@babel/code-frame@^7.0.0":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==
dependencies:
"@babel/highlight" "^7.16.7"
"@babel/helper-validator-identifier@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==
"@babel/highlight@^7.16.7":
version "7.16.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88"
integrity sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==
dependencies:
"@babel/helper-validator-identifier" "^7.16.7"
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.16.4":
version "7.17.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240"
integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==
"@headlessui/vue@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@headlessui/vue/-/vue-1.5.0.tgz#8cd4877d3d344e482073a04ae8e3b7f987bb267c"
integrity sha512-jAp6XYpqdEv32xhszaj5ejvjaX5qhu20sCbxu7lplePEfZL+4ffabivJBBTZAiPWczqAXmnZWNWG5DOyqjRa4w==
"@heroicons/vue@^1.0.6":
version "1.0.6"
resolved "https://registry.yarnpkg.com/@heroicons/vue/-/vue-1.0.6.tgz#d8b90734b436eb5a87f40cc300b64a0fb0031f7f"
integrity sha512-ng2YcCQrdoQWEFpw+ipFl2rZo8mZ56v0T5+MyfQQvNqfKChwgP6DMloZLW+rl17GEcHkE3H82UTAMKBKZr4+WA==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
dependencies:
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
dependencies:
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@tailwindcss/typography@^0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.2.tgz#24b069dab24d7a2467d01aca0dd432cb4b29f0ee"
integrity sha512-coq8DBABRPFcVhVIk6IbKyyHUt7YTEC/C992tatFB+yEx5WGBQrCgsSFjxHUr8AWXphWckadVJbominEduYBqw==
dependencies:
lodash.castarray "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2"
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@vitejs/plugin-vue@^2.3.0":
version "2.3.1"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-2.3.1.tgz#5f286b8d3515381c6d5c8fa8eee5e6335f727e14"
integrity sha512-YNzBt8+jt6bSwpt7LP890U1UcTOIZZxfpE5WOJ638PNxSEKOqAi0+FSKS0nVeukfdZ0Ai/H7AFd6k3hayfGZqQ==
"@vue/compiler-core@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.31.tgz#d38f06c2cf845742403b523ab4596a3fda152e89"
integrity sha512-aKno00qoA4o+V/kR6i/pE+aP+esng5siNAVQ422TkBNM6qA4veXiZbSe8OTXHXquEi/f6Akc+nLfB4JGfe4/WQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
source-map "^0.6.1"
"@vue/compiler-dom@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.31.tgz#b1b7dfad55c96c8cc2b919cd7eb5fd7e4ddbf00e"
integrity sha512-60zIlFfzIDf3u91cqfqy9KhCKIJgPeqxgveH2L+87RcGU/alT6BRrk5JtUso0OibH3O7NXuNOQ0cDc9beT0wrg==
dependencies:
"@vue/compiler-core" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/compiler-sfc@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.31.tgz#d02b29c3fe34d599a52c5ae1c6937b4d69f11c2f"
integrity sha512-748adc9msSPGzXgibHiO6T7RWgfnDcVQD+VVwYgSsyyY8Ans64tALHZANrKtOzvkwznV/F4H7OAod/jIlp/dkQ==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.31"
"@vue/compiler-dom" "3.2.31"
"@vue/compiler-ssr" "3.2.31"
"@vue/reactivity-transform" "3.2.31"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
magic-string "^0.25.7"
postcss "^8.1.10"
source-map "^0.6.1"
"@vue/compiler-ssr@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.31.tgz#4fa00f486c9c4580b40a4177871ebbd650ecb99c"
integrity sha512-mjN0rqig+A8TVDnsGPYJM5dpbjlXeHUm2oZHZwGyMYiGT/F4fhJf/cXy8QpjnLQK4Y9Et4GWzHn9PS8AHUnSkw==
dependencies:
"@vue/compiler-dom" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/devtools-api@^6.0.0":
version "6.1.4"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.1.4.tgz#b4aec2f4b4599e11ba774a50c67fa378c9824e53"
integrity sha512-IiA0SvDrJEgXvVxjNkHPFfDx6SXw0b/TUkqMcDZWNg9fnCAHbTpoo59YfJ9QLFkwa3raau5vSlRVzMSLDnfdtQ==
"@vue/reactivity-transform@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.31.tgz#0f5b25c24e70edab2b613d5305c465b50fc00911"
integrity sha512-uS4l4z/W7wXdI+Va5pgVxBJ345wyGFKvpPYtdSgvfJfX/x2Ymm6ophQlXXB6acqGHtXuBqNyyO3zVp9b1r0MOA==
dependencies:
"@babel/parser" "^7.16.4"
"@vue/compiler-core" "3.2.31"
"@vue/shared" "3.2.31"
estree-walker "^2.0.2"
magic-string "^0.25.7"
"@vue/reactivity@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.31.tgz#fc90aa2cdf695418b79e534783aca90d63a46bbd"
integrity sha512-HVr0l211gbhpEKYr2hYe7hRsV91uIVGFYNHj73njbARVGHQvIojkImKMaZNDdoDZOIkMsBc9a1sMqR+WZwfSCw==
dependencies:
"@vue/shared" "3.2.31"
"@vue/runtime-core@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.31.tgz#9d284c382f5f981b7a7b5971052a1dc4ef39ac7a"
integrity sha512-Kcog5XmSY7VHFEMuk4+Gap8gUssYMZ2+w+cmGI6OpZWYOEIcbE0TPzzPHi+8XTzAgx1w/ZxDFcXhZeXN5eKWsA==
dependencies:
"@vue/reactivity" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/runtime-dom@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.31.tgz#79ce01817cb3caf2c9d923f669b738d2d7953eff"
integrity sha512-N+o0sICVLScUjfLG7u9u5XCjvmsexAiPt17GNnaWHJUfsKed5e85/A3SWgKxzlxx2SW/Hw7RQxzxbXez9PtY3g==
dependencies:
"@vue/runtime-core" "3.2.31"
"@vue/shared" "3.2.31"
csstype "^2.6.8"
"@vue/server-renderer@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.31.tgz#201e9d6ce735847d5989403af81ef80960da7141"
integrity sha512-8CN3Zj2HyR2LQQBHZ61HexF5NReqngLT3oahyiVRfSSvak+oAvVmu8iNLSu6XR77Ili2AOpnAt1y8ywjjqtmkg==
dependencies:
"@vue/compiler-ssr" "3.2.31"
"@vue/shared" "3.2.31"
"@vue/shared@3.2.31":
version "3.2.31"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.31.tgz#c90de7126d833dcd3a4c7534d534be2fb41faa4e"
integrity sha512-ymN2pj6zEjiKJZbrf98UM2pfDd6F2H7ksKw7NDt/ZZ1fh5Ei39X5tABugtT03ZRlWd9imccoK0hE8hpjpU7irQ==
acorn-node@^1.6.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
dependencies:
acorn "^7.0.0"
acorn-walk "^7.0.0"
xtend "^4.0.2"
acorn-walk@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn@^7.0.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
ansi-styles@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb"
integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==
autoprefixer@^10.4.4:
version "10.4.4"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e"
integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==
dependencies:
browserslist "^4.20.2"
caniuse-lite "^1.0.30001317"
fraction.js "^4.2.0"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies:
fill-range "^7.0.1"
browserslist@^4.20.2:
version "4.20.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
dependencies:
caniuse-lite "^1.0.30001317"
electron-to-chromium "^1.4.84"
escalade "^3.1.1"
node-releases "^2.0.2"
picocolors "^1.0.0"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001317:
version "1.0.30001325"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz#2b4ad19b77aa36f61f2eaf72e636d7481d55e606"
integrity sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==
chalk@^2.0.0:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
glob-parent "~5.1.2"
is-binary-path "~2.1.0"
is-glob "~4.0.1"
normalize-path "~3.0.0"
readdirp "~3.6.0"
optionalDependencies:
fsevents "~2.3.2"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
cosmiconfig@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d"
integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==
dependencies:
"@types/parse-json" "^4.0.0"
import-fresh "^3.2.1"
parse-json "^5.0.0"
path-type "^4.0.0"
yaml "^1.10.0"
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@^2.6.8:
version "2.6.20"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda"
integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==
defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
detective@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==
dependencies:
acorn-node "^1.6.1"
defined "^1.0.0"
minimist "^1.1.1"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
dlv@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
electron-to-chromium@^1.4.84:
version "1.4.103"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.103.tgz#abfe376a4d70fa1e1b4b353b95df5d6dfd05da3a"
integrity sha512-c/uKWR1Z/W30Wy/sx3dkZoj4BijbXX85QKWu9jJfjho3LBAXNEGAEW3oWiGb+dotA6C6BzCTxL2/aLes7jlUeg==
error-ex@^1.3.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
dependencies:
is-arrayish "^0.2.1"
esbuild-android-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.30.tgz#9efcdb3d826b9c67705a0c518d361ab44ae4cc5b"
integrity sha512-vdJ7t8A8msPfKpYUGUV/KaTQRiZ0vDa2XSTlzXVkGGVHLKPeb85PBUtYJcEgw3htW3IdX5i1t1IMdQCwJJgNAg==
esbuild-android-arm64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.30.tgz#7b90fa7578b94da047e0bf3da477cb5775b58664"
integrity sha512-BdgGfxeA5hBQNErLr7BWJUA8xjflEfyaARICy8e0OJYNSAwDbEzOf8LyiKWSrDcgV129mWhi3VpbNQvOIDEHcg==
esbuild-darwin-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.30.tgz#275e40eb100286b868d4cd664e6447b53400f7ff"
integrity sha512-VRaOXMMrsG5n53pl4qFZQdXy2+E0NoLP/QH3aDUI0+bQP+ZHDmbINKcDy2IX7GVFI9kqPS18iJNAs5a6/G2LZg==
esbuild-darwin-arm64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.30.tgz#c2b8fe03fb0dcee1d227b226c9c921de71e4d411"
integrity sha512-qDez+fHMOrO9Oc9qjt/x+sy09RJVh62kik5tVybKRLmezeV4qczM9/sAYY57YN0aWLdHbcCj2YqJUWYJNsgKnw==
esbuild-freebsd-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.30.tgz#328bb272ce6cfcae202b6e0a06c240036335cc91"
integrity sha512-mec1jENcImVVagddZlGWsdAUwBnzR5cgnhzCxv+9fSMxKbx1uZYLLUAnLPp8m/i934zrumR1xGjJ5VoWdPlI2w==
esbuild-freebsd-arm64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.30.tgz#49ce79fa7d7087a941e27432da46bafb5b35a979"
integrity sha512-cpjbTs6Iok/AfeB0JgTzyUJTMStC1SQULmany5nHx6S4GTkSgaAHuJzZO0GcVWqghI4e0YL/bjXAhN5Mn6feNw==
esbuild-linux-32@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.30.tgz#c473299f5291130f6d4436808603b529ccbfa785"
integrity sha512-liIONVT4F2kZmOMwtwASqZ8WkIjb5HHBR9HUffdHiuotSTF3CyZO+EJf+Og+SYYuuVIvt0qHNSFjBA/iSESteQ==
esbuild-linux-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.30.tgz#b4be59f0665a44574a7617c9db673943da6061f8"
integrity sha512-LUnpzoMpRqFON5En4qEj6NWiyH6a1K+Y2qYNKrCy5qPTjDoG/EWeqMz69n8Uv7pRuvDKl3FNGJ1dufTrA5i0sw==
esbuild-linux-arm64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.30.tgz#78485a9f49858ea69ced85c3dd0fe58d354f20f8"
integrity sha512-DHZHn6FK5q/KL0fpNT/0jE38Nnyk2rXxKE9WENi95EXtqfOLPgE8tzjTZQNgpr61R95QX4ymQU26ni3IZk8buQ==
esbuild-linux-arm@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.30.tgz#00392bbe04ac687dd6d9849ecee302bc27f984f2"
integrity sha512-97T+bbXnpqf7mfIG49UR7ZSJFGgvc22byn74qw3Kx2GDCBSQoVFjyWuKOHGXp8nXk3XYrdFF+mQ8yQ7aNsgQvg==
esbuild-linux-mips64le@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.30.tgz#cf48a292037c3a9ca3ca84ec0ae4f47bf1247ab4"
integrity sha512-fLUzTFZ7uknC0aPTk7/lM7NmaG/9ZqE3SaHEphcaM009SZK/mDOvZugWi1ss6WGNhk13dUrhkfHcc4FSb9hYhg==
esbuild-linux-ppc64le@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.30.tgz#8e448a54e7040829ee9ecfc68dab0f3a039b1cff"
integrity sha512-2Oudm2WEfj0dNU9bzIl5L/LrsMEmHWsOsYgJJqu8fDyUDgER+J1d33qz3cUdjsJk7gAENayIxDSpsuCszx0w3A==
esbuild-linux-riscv64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.30.tgz#118af007f21adb00c4362d5eda3f004290f4d0ef"
integrity sha512-RPMucPW47rV4t2jlelaE948iCRtbZf5RhifxSwzlpM1Mqdyu99MMNK0w4jFreGTmLN+oGomxIOxD6n+2E/XqHw==
esbuild-linux-s390x@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.30.tgz#e0649e26d8791bf6265685842dcef732de59b49b"
integrity sha512-OZ68r7ok6qO7hdwrwQn2p5jbIRRcUcVaAykB7e0uCA0ODwfeGunILM6phJtq2Oz4dlEEFvd+tSuma3paQKwt+A==
esbuild-netbsd-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.30.tgz#7653db21ab4379a1b557f338b040e3493ff5006c"
integrity sha512-iyejQUKn0TzpPkufq8pSCxOg9NheycQbMbPCmjefTe9wYuUlBt1TcHvdoJnYbQzsAhAh1BNq+s0ycRsIJFZzaQ==
esbuild-openbsd-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.30.tgz#3fad1699cb2ca7a060585356e2881df7b3fa5bb1"
integrity sha512-UyK1MTMcy4j5fH260fsE1o6MVgWNhb62eCK2yCKCRazZv8Nqdc2WiP9ygjWidmEdCDS+A6MuVp9ozk9uoQtQpA==
esbuild-sunos-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.30.tgz#2306a178dc2362cf32fc182327c21b49b78ca1a3"
integrity sha512-aQRtRTNKHB4YuG+xXATe5AoRTNY48IJg5vjE8ElxfmjO9+KdX7MHFkTLhlKevCD6rNANtB3qOlSIeAiXTwHNqw==
esbuild-windows-32@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.30.tgz#ea17b2a9468e346734f926a7edd49de3cc81d12f"
integrity sha512-9/fb1tPtpacMqxAXp3fGHowUDg/l9dVch5hKmCLEZC6PdGljh6h372zMdJwYfH0Bd5CCPT0Wx95uycBLJiqpXA==
esbuild-windows-64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.30.tgz#266147f1f45974ac61dece10ab32bc916050dcc2"
integrity sha512-DHgITeUhPAnN9I5O6QBa1GVyPOhiYCn4S4TtQr7sO4+X0LNyqnlmA1M0qmGkUdDC1QQfjI8uQ4G/whdWb2pWIQ==
esbuild-windows-arm64@0.14.30:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.30.tgz#8e5e9c2455bffaf3e1a387eee4841f5c3797b8a8"
integrity sha512-F1kLyQH7zSgjh5eLxogGZN7C9+KNs9m+s7Q6WZoMmCWT/6j998zlaoECHyM8izJRRfsvw2eZlEa1jO6/IOU1AQ==
esbuild@^0.14.27:
version "0.14.30"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.30.tgz#8bf6501a58f71e622e7485aeada79c764c1affe4"
integrity sha512-wCecQSBkIjp2xjuXY+wcXS/PpOQo9rFh4NAKPh4Pm9f3fuLcnxkR0rDzA+mYP88FtXIUcXUyYmaIgfrzRl55jA==
optionalDependencies:
esbuild-android-64 "0.14.30"
esbuild-android-arm64 "0.14.30"
esbuild-darwin-64 "0.14.30"
esbuild-darwin-arm64 "0.14.30"
esbuild-freebsd-64 "0.14.30"
esbuild-freebsd-arm64 "0.14.30"
esbuild-linux-32 "0.14.30"
esbuild-linux-64 "0.14.30"
esbuild-linux-arm "0.14.30"
esbuild-linux-arm64 "0.14.30"
esbuild-linux-mips64le "0.14.30"
esbuild-linux-ppc64le "0.14.30"
esbuild-linux-riscv64 "0.14.30"
esbuild-linux-s390x "0.14.30"
esbuild-netbsd-64 "0.14.30"
esbuild-openbsd-64 "0.14.30"
esbuild-sunos-64 "0.14.30"
esbuild-windows-32 "0.14.30"
esbuild-windows-64 "0.14.30"
esbuild-windows-arm64 "0.14.30"
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
estree-walker@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
fast-glob@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
dependencies:
reusify "^1.0.4"
fill-range@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
dependencies:
to-regex-range "^5.0.1"
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies:
is-glob "^4.0.1"
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
is-glob "^4.0.3"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-core-module@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
is-number@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
lilconfig@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
lines-and-columns@^1.1.6:
version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
lodash.castarray@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115"
integrity sha1-wCUTUV4wna3dTCTGDP3c9ZdtkRU=
lodash.isplainobject@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb"
integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
magic-string@^0.25.7:
version "0.25.9"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
dependencies:
sourcemap-codec "^1.4.8"
merge2@^1.3.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4:
version "4.0.5"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
dependencies:
braces "^3.0.2"
picomatch "^2.3.1"
minimist@^1.1.1:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
nanoid@^3.3.1:
version "3.3.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557"
integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==
node-releases@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
normalize-range@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
notiwind@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/notiwind/-/notiwind-1.2.5.tgz#4a1ab3d3afa0c88f6539c6730d619feb9d528230"
integrity sha512-i0WHk96skvQ0xt3uMeAI+tVZ2sgAOSpuNIeTycQZjwXh4DheLbpJffLjYOXT93C3CqZYVTal0hnGCpZIlG0jgQ==
object-hash@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
dependencies:
callsites "^3.0.0"
parse-json@^5.0.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
dependencies:
"@babel/code-frame" "^7.0.0"
error-ex "^1.3.1"
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
postcss-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
dependencies:
camelcase-css "^2.0.1"
postcss-load-config@^3.1.0:
version "3.1.4"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
dependencies:
lilconfig "^2.0.5"
yaml "^1.10.2"
postcss-nested@5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
dependencies:
postcss-selector-parser "^6.0.6"
postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.1.10, postcss@^8.4.12, postcss@^8.4.6:
version "8.4.12"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
dependencies:
nanoid "^3.3.1"
picocolors "^1.0.0"
source-map-js "^1.0.2"
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.22.0:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
dependencies:
is-core-module "^2.8.1"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rollup@^2.59.0:
version "2.70.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.70.1.tgz#824b1f1f879ea396db30b0fc3ae8d2fead93523e"
integrity sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==
optionalDependencies:
fsevents "~2.3.2"
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
dependencies:
queue-microtask "^1.2.2"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
dependencies:
has-flag "^3.0.0"
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
tailwindcss@^3.0.23:
version "3.0.23"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.23.tgz#c620521d53a289650872a66adfcb4129d2200d10"
integrity sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==
dependencies:
arg "^5.0.1"
chalk "^4.1.2"
chokidar "^3.5.3"
color-name "^1.1.4"
cosmiconfig "^7.0.1"
detective "^5.2.0"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.2.11"
glob-parent "^6.0.2"
is-glob "^4.0.3"
normalize-path "^3.0.0"
object-hash "^2.2.0"
postcss "^8.4.6"
postcss-js "^4.0.0"
postcss-load-config "^3.1.0"
postcss-nested "5.0.6"
postcss-selector-parser "^6.0.9"
postcss-value-parser "^4.2.0"
quick-lru "^5.1.1"
resolve "^1.22.0"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
dependencies:
is-number "^7.0.0"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
vite@^2.9.0:
version "2.9.1"
resolved "https://registry.yarnpkg.com/vite/-/vite-2.9.1.tgz#84bce95fae210a7beb566a0af06246748066b48f"
integrity sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ==
dependencies:
esbuild "^0.14.27"
postcss "^8.4.12"
resolve "^1.22.0"
rollup "^2.59.0"
optionalDependencies:
fsevents "~2.3.2"
vue-router@^4.0.14:
version "4.0.14"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.14.tgz#ce2028c1c5c33e30c7287950c973f397fce1bd65"
integrity sha512-wAO6zF9zxA3u+7AkMPqw9LjoUCjSxfFvINQj3E/DceTt6uEz1XZLraDhdg2EYmvVwTBSGlLYsUw8bDmx0754Mw==
dependencies:
"@vue/devtools-api" "^6.0.0"
vue@^3.2.25:
version "3.2.31"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.31.tgz#e0c49924335e9f188352816788a4cca10f817ce6"
integrity sha512-odT3W2tcffTiQCy57nOT93INw1auq5lYLLYtWpPYQQYQOOdHiqFct9Xhna6GJ+pJQaF67yZABraH47oywkJgFw==
dependencies:
"@vue/compiler-dom" "3.2.31"
"@vue/compiler-sfc" "3.2.31"
"@vue/runtime-dom" "3.2.31"
"@vue/server-renderer" "3.2.31"
"@vue/shared" "3.2.31"
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yaml@^1.10.0, yaml@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==

View File

@@ -16,9 +16,7 @@ import (
"sync"
"github.com/rs/zerolog"
"gopkg.in/tomb.v2"
"akvorado/common/daemon"
"akvorado/common/http"
"akvorado/common/reporter"
)
@@ -27,7 +25,6 @@ import (
type Component struct {
r *reporter.Reporter
d *Dependencies
t tomb.Tomb
config Configuration
templates map[string]*template.Template
@@ -36,8 +33,7 @@ type Component struct {
// Dependencies define the dependencies of the console component.
type Dependencies struct {
Daemon daemon.Component
HTTP *http.Component
HTTP *http.Component
}
// New creates a new console component.
@@ -47,10 +43,6 @@ func New(reporter *reporter.Reporter, config Configuration, dependencies Depende
d: &dependencies,
config: config,
}
c.d.Daemon.Track(&c.t, "console")
if err := c.loadTemplates(); err != nil {
return nil, err
}
// Grafana proxy
if c.config.GrafanaURL != "" {
@@ -74,35 +66,12 @@ func New(reporter *reporter.Reporter, config Configuration, dependencies Depende
c.d.HTTP.AddHandler("/grafana/", proxyHandler)
}
c.d.HTTP.AddHandler("/docs/", netHTTP.HandlerFunc(c.docsHandlerFunc))
c.d.HTTP.AddHandler("/assets/", netHTTP.HandlerFunc(c.assetsHandlerFunc))
c.d.HTTP.AddHandler("/", netHTTP.HandlerFunc(c.assetsHandlerFunc))
c.d.HTTP.AddHandler("/api/v0/docs/", netHTTP.HandlerFunc(c.docsHandlerFunc))
return &c, nil
}
// Start starts the web component.
func (c *Component) Start() error {
c.r.Info().Msg("starting console component")
if err := c.watchTemplates(); err != nil {
return err
}
c.t.Go(func() error {
select {
case <-c.t.Dying():
return nil
}
})
return nil
}
// Stop stops the console component.
func (c *Component) Stop() error {
c.r.Info().Msg("stopping console component")
defer c.r.Info().Msg("console component stopped")
c.t.Kill(nil)
return c.t.Wait()
}
// embedOrLiveFS returns a subset of the provided embedded filesystem,
// except if the component is configured to use the live filesystem.
// Then, it returns the provided tree.

View File

@@ -7,7 +7,6 @@ import (
"net/http/httptest"
"testing"
"akvorado/common/daemon"
"akvorado/common/helpers"
"akvorado/common/http"
"akvorado/common/reporter"
@@ -27,8 +26,7 @@ func TestProxy(t *testing.T) {
_, err := New(r, Configuration{
GrafanaURL: server.URL,
}, Dependencies{
HTTP: h,
Daemon: daemon.NewMock(t),
HTTP: h,
})
if err != nil {
t.Fatalf("New() error:\n%+v", err)

View File

@@ -1,153 +0,0 @@
package console
import (
"akvorado/common/reporter"
"bytes"
"embed"
"fmt"
"html/template"
"io"
"io/fs"
"net/http"
"path"
"path/filepath"
"runtime"
"time"
"github.com/Masterminds/sprig"
"github.com/fsnotify/fsnotify"
)
//go:embed data/templates
var embeddedTemplates embed.FS
// baseData is the data to pass for all templates.
type templateBaseData struct {
RootPath string
CurrentPath string
}
// loadTemplates reload the templates.
func (c *Component) loadTemplates() error {
mainTemplate, err := template.
New("main").
Option("missingkey=error").
Funcs(sprig.FuncMap()).
Parse(`{{define "main" }}{{ template "base" . }}{{ end }}`)
if err != nil {
c.r.Err(err).Msg("unable to create main template")
return fmt.Errorf("unable to create main template: %w", err)
}
layoutFiles := c.embedOrLiveFS(embeddedTemplates, "data/templates/layout")
templateFiles := c.embedOrLiveFS(embeddedTemplates, "data/templates")
compiled := make(map[string]*template.Template)
entries, err := fs.ReadDir(templateFiles, ".")
if err != nil {
c.r.Err(err).Msg("unable to list template files")
return fmt.Errorf("unable to list template files: %w", err)
}
for _, tpl := range entries {
if tpl.IsDir() {
continue
}
template, err := mainTemplate.Clone()
if err != nil {
c.r.Err(err).Msg("unable to clone main template")
return fmt.Errorf("unable to clone main template: %w", err)
}
f, err := templateFiles.Open(tpl.Name())
if err != nil {
c.r.Err(err).Str("template", tpl.Name()).Msg("unable to open template")
return fmt.Errorf("unable to open template %q: %w", tpl.Name(), err)
}
content, err := io.ReadAll(f)
if err != nil {
f.Close()
c.r.Err(err).Str("template", tpl.Name()).Msg("unable to read template")
return fmt.Errorf("unable to read template %q: %w", tpl.Name(), err)
}
f.Close()
template, err = template.Parse(string(content))
if err != nil {
c.r.Err(err).Str("template", tpl.Name()).Msg("unable to parse template")
return fmt.Errorf("unable to parse template %q: %w", tpl.Name(), err)
}
template, err = template.ParseFS(layoutFiles, "*.html")
if err != nil {
c.r.Err(err).Msg("unable to parse layout templates")
return fmt.Errorf("unable to parse layout templates: %w", err)
}
compiled[tpl.Name()] = template
}
c.templatesLock.Lock()
c.templates = compiled
c.templatesLock.Unlock()
return nil
}
// renderTemplate render the specified template
func (c *Component) renderTemplate(w http.ResponseWriter, name string, data interface{}) {
c.templatesLock.RLock()
tmpl, ok := c.templates[name]
c.templatesLock.RUnlock()
if !ok {
c.r.Error().Str("template", name).Msg("template not found")
http.Error(w, fmt.Sprintf("No template %q found.", name), http.StatusNotFound)
return
}
buf := &bytes.Buffer{}
if err := tmpl.Execute(buf, data); err != nil {
c.r.Err(err).Str("template", name).Msg("error while rendering template")
http.Error(w, fmt.Sprintf("Error while rendering %q.", name), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
}
// watchTemplates monitor changes in template directories and reload them
func (c *Component) watchTemplates() error {
if !c.config.ServeLiveFS {
return nil
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
c.r.Err(err).Msg("cannot setup watcher for templates")
return fmt.Errorf("cannot setup watcher: %w", err)
}
for _, dir := range []string{"templates", "templates/layout"} {
_, base, _, _ := runtime.Caller(0)
dir = filepath.Join(path.Dir(base), "data", dir)
if err := watcher.Add(dir); err != nil {
c.r.Err(err).Str("directory", dir).Msg("cannot watch template directory")
return fmt.Errorf("cannot watch template directory %q: %w", dir, err)
}
}
c.t.Go(func() error {
defer watcher.Close()
errLogger := c.r.Sample(reporter.BurstSampler(10*time.Second, 1))
timer := time.NewTimer(100 * time.Millisecond)
for {
select {
case <-c.t.Dying():
return nil
case err := <-watcher.Errors:
errLogger.Err(err).Msg("error from watcher")
case event := <-watcher.Events:
if event.Op&(fsnotify.Write|fsnotify.Create) == 0 {
continue
}
timer.Stop()
timer.Reset(500 * time.Millisecond)
case <-timer.C:
c.r.Info().Msg("reload templates")
c.loadTemplates() // errors are ignored
}
}
})
return nil
}

View File

@@ -1,60 +0,0 @@
package console
import (
"net/http/httptest"
"strings"
"testing"
"time"
"akvorado/common/daemon"
"akvorado/common/http"
"akvorado/common/reporter"
)
func TestTemplate(t *testing.T) {
for _, live := range []bool{false, true} {
name := "livefs"
if !live {
name = "embeddedfs"
}
t.Run(name, func(t *testing.T) {
r := reporter.NewMock(t)
c, err := New(r, Configuration{
ServeLiveFS: live,
}, Dependencies{
HTTP: http.NewMock(t, r),
Daemon: daemon.NewMock(t),
})
if err != nil {
t.Fatalf("New() error:\n%+v", err)
}
if err := c.Start(); err != nil {
t.Fatalf("Start() error:\n%+v", err)
}
defer func() {
if err := c.Stop(); err != nil {
t.Fatalf("Stop() error:\n%+v", err)
}
}()
w := httptest.NewRecorder()
c.renderTemplate(w, "dummy.html", templateBaseData{
RootPath: ".",
})
if w.Code != 200 {
t.Errorf("renderTemplate() code was %d, expected 200", w.Code)
}
body := strings.TrimSpace(w.Body.String())
if !strings.HasPrefix(body, "<!doctype html>") {
t.Errorf("renderTemplate() body should contain <!doctype html>, got:\n%s",
body)
}
if live && !testing.Short() {
// Wait for refresh of templates to happen.
time.Sleep(200 * time.Millisecond)
}
})
}
}