Compare commits

...

82 Commits

Author SHA1 Message Date
Daniel Valdivia
fbb6c81986 Release v0.14.3 (#1520)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-02-04 20:10:34 -08:00
Kaan Kabalak
9e843f4ba0 Add concurrency argument for Permission testing (#1510)
* Add concurrency argument for Permission testing

* Create group before trying to view table

* Increase wait duration for one of the Diagnostic tests
2022-02-04 09:52:28 -06:00
Harshavardhana
4e14ec2742 update mc/termenv dependency to fix terminal hang (#1518)
refer https://github.com/minio/mc/issues/3955
2022-02-03 22:44:45 -08:00
Alex
69ccf4872e Added virtualized list to pod logs in operator console (#1517)
Also removed some execution time errors
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-02-03 22:19:17 -08:00
Prakash Senthil Vel
1c31aff147 UX Basic Dashboard (#1513) 2022-02-03 21:34:13 -08:00
Kaan Kabalak
608a5c3787 Fix User name clicked on Groups page redirecting incorrectly (#1516)
Fixes #1515
2022-02-03 19:07:23 -08:00
Lenin Alevski
297c980a8d Read subnet proxy configuration from minio or env var (#1511)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-02-03 10:04:35 -08:00
Alex
4091b11f99 Fixed Node Selector reset in Pod placement (#1512) 2022-02-03 08:41:35 -08:00
Harshavardhana
4718380bd2 update termenv dependency to avoid CI/CD terminal hangs (#1507) 2022-02-02 23:14:11 -08:00
Alex
52ea19809e Customized calendar & time pickers for DateTimePicker component (#1508)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-02-02 23:21:46 -06:00
dependabot[bot]
281bd78104 Bump nanoid from 3.1.30 to 3.2.0 in /portal-ui (#1509)
Bumps [nanoid](https://github.com/ai/nanoid) from 3.1.30 to 3.2.0.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.1.30...3.2.0)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-02 21:30:38 -06:00
Daniel Valdivia
d84e744d05 Update UI Dependencies (#1505)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-02-02 18:38:20 -08:00
Harshavardhana
1b5bcad3d8 update missing base image update to ubi-8.5 2022-02-02 13:20:05 -08:00
Daniel Valdivia
bfc4b138ff Update unit test list buckets (#1482)
Update unit test list buckets
2022-02-02 12:28:03 -08:00
Lenin Alevski
5a0ec11199 Updating node image and go image in Dockerfile (#1504)
- Updating node image to 17
- Updating go image to 1.17

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-02-02 13:44:43 -06:00
Cesar Celis Hernandez
c52ba1f859 Test to delete multiple objects (#1479) 2022-02-02 13:29:43 -06:00
Cesar Celis Hernandez
d62235ee58 Add test for listing an object (#1484)
* Add test for listing an object

* Add test for listing an object
2022-02-02 13:06:01 -06:00
jinapurapu
e5d2752436 Added multidelete function for Service Accounts (#1501)
Added multidelete function for Service Accounts
2022-02-02 10:21:56 -08:00
Harshavardhana
cffaee84bb upgrade console container base image to ubi-8.5 (#1503) 2022-02-02 08:53:13 -08:00
Alex
96b1d4fe85 Added bucket details inside objects listing (#1502)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2022-02-01 20:48:58 -08:00
Cesar Celis Hernandez
02acb76ac9 Add test for Bulk functionality to Add Users to Groups (#1495)
Co-authored-by: Lenin Alevski <alevsk.8772@gmail.com>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-02-01 08:42:09 -08:00
Harshavardhana
038f542895 go mod tidy -compat=1.17 2022-01-31 23:39:28 -08:00
Daniel Valdivia
983e175bc9 Release v0.14.2 (#1499)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-31 23:33:57 -08:00
Alex
71ac8d7001 Replaced top breadcrumbs & redesign of bucket breadcrumbs (#1498) 2022-01-31 23:40:23 -07:00
jinapurapu
5258ac3d1a Logging resources selector (#1402)
Added CPU and memory request selector to Log and Log DB
2022-01-31 21:17:21 -08:00
Daniel Valdivia
27d1627c8f Show Warnings when running speed test or diagnostics (#1487)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-31 20:41:02 -08:00
Alex
780cf7240b Changed position of search box for list objects module (#1497)
- Changed the searchbox component to be a controlled component
- Added reducer to control searchbox in objects list
- Adjusted styles to search box component & placeholders
- Fixed navigation of folders

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2022-01-31 19:46:57 -08:00
Cesar Celis Hernandez
ce989e39ab Add test to add tag to an object (#1481) 2022-01-31 20:46:40 -06:00
adfost
63d3c7207d Service Account Policy API (#1425)
* service account policy

* integration test

Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-31 13:37:00 -08:00
Lenin Alevski
eb924ec842 testscafe test for upload file button on bucket (#1491)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>

Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-31 12:00:33 -08:00
Lenin Alevski
f826453284 Fix duplicated Documentation item in Menu (#1494)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-01-31 13:53:49 -06:00
Harshavardhana
1e00111b00 remove quota FIFO support (#1492) 2022-01-31 11:10:06 -06:00
Cesar Celis Hernandez
06e1592b54 Added test for downloading an object (#1480) 2022-01-31 10:25:35 -06:00
Alex
41e0c1e39b Fixed EC Parity field reset during tab change (#1490) 2022-01-29 16:28:57 -06:00
Alex
31f63a387e Added object details panel (#1489)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2022-01-28 20:05:23 -08:00
Cesar Celis Hernandez
4a1ccf19a0 Removing unused parameters (#1465) 2022-01-28 21:24:53 -06:00
Lenin Alevski
d8b387434b Enable/Disable upload file and folder button for bucket (#1486) 2022-01-28 14:16:38 -08:00
Cesar Celis Hernandez
2e9a42320c Test to delete object (#1477) 2022-01-28 12:01:38 -06:00
Prakash Senthil Vel
1d92f90cbe UX Register screen (#1485) 2022-01-28 09:08:32 -08:00
Harshavardhana
df728fc8e6 update madmin-go (#1483) 2022-01-27 23:57:30 -07:00
Cesar Celis Hernandez
834e3fb996 Add test to upload an object (#1473)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-27 21:51:13 -08:00
Cesar Celis Hernandez
0b7d4a2c35 Add delete bucket test (#1457)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-27 17:56:09 -08:00
Cesar Celis Hernandez
e13626e92b Add new test for service account for user (#1469)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-27 17:31:13 -08:00
Lenin Alevski
0286010053 Fix regression on disabled elements for SecureComponent (#1478)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-01-27 13:10:05 -08:00
Harshavardhana
382e315668 remove an unnecessary log in subnet pkg (#1471)
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-27 13:27:44 -06:00
Prakash Senthil Vel
5f5c00adb5 UX Diagnostic icon (#1474) 2022-01-27 12:47:42 -06:00
Prakash Senthil Vel
e093efa931 UX Object Details page (#1475)
Co-authored-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-01-27 09:23:21 -08:00
Alex
c129eae6a7 Bucket objects listing menu redesign (#1467)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2022-01-27 00:28:08 -08:00
Daniel Valdivia
5b6e5786ea Release v0.14.1 (#1472)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-26 19:24:36 -08:00
Lenin Alevski
8a8471e49a Support multiple child components for SecureComponent (#1470)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-01-26 16:59:12 -08:00
Lenin Alevski
719866a574 Fix broken link on license page (#1468)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-01-26 15:59:07 -08:00
Cesar Celis Hernandez
95ebc3bedf add test to modify users group (#1462)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-26 11:38:14 -08:00
Daniel Valdivia
a8747614bf Release v0.14.0 (#1458)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-26 11:06:18 -08:00
Daniel Valdivia
c89f5a7003 Tweaks to registration. (#1463)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-26 10:21:53 -08:00
Daniel Valdivia
ebcebfbe5f Update MUI to v5.3.1 (#1464)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-26 12:12:16 -06:00
Lenin Alevski
c82782fe9f Adding support for configuring subnet proxy (#1460)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-01-26 09:53:11 -08:00
Cesar Celis Hernandez
e626f59feb Add bucket retention test (#1459)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-25 16:33:54 -08:00
adfost
cc8d5abcd6 Adding PVC events UI (#1448)
* adding PVC events UI

* adding label

Co-authored-by: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-25 14:02:50 -08:00
Daniel Valdivia
83a4c351dd Fix diagnostics tests (#1456)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-25 13:24:19 -08:00
Cesar Celis Hernandez
4c0c46f5a8 Adding Bucket Info Test (#1446)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-25 12:16:51 -08:00
Daniel Valdivia
1842caff0f Fix secure component having multiple childs (#1455)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-25 11:49:39 -08:00
Lenin Alevski
80d3e8cdb8 Menu scrollbar css styles (#1454)
Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>

Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-25 10:58:29 -08:00
Harshavardhana
d936d61b20 add header linter to avoid license header mistakes (#1414)
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-25 10:48:49 -08:00
adfost
4a10a81374 Delete Pod UI (#1381)
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-25 10:33:51 -08:00
Cesar Celis Hernandez
6404a1b984 Add test for removing a user (#1450)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-25 10:23:25 -08:00
Prakash Senthil Vel
a3dc145738 UX navigation menu selected state (#1452)
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-25 10:07:41 -08:00
Alex
dbd1b8781a Fix aria for upload file button (#1451) 2022-01-25 00:26:48 -07:00
adfost
9e7874cc04 PVC Events API (#1409)
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-24 17:07:32 -08:00
Alex
d1d3c96777 Added Upload Files menu (#1447)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2022-01-24 16:53:47 -08:00
Daniel Valdivia
2d975eb6c9 Remove Duck hidden in code (#1445)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-24 15:53:23 -08:00
Daniel Valdivia
be054fe4ce License Page UI Updates (#1444)
* License Page UI Updates

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Lint

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-24 17:40:38 -06:00
Daniel Valdivia
f27902ab92 Have tests create their buckets via minio-js (#1443)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-24 11:13:32 -08:00
Cesar Celis Hernandez
7e43719e20 Update User Information Test (#1442)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-24 10:09:27 -08:00
Daniel Valdivia
f6016c2769 Menu Adjustments: Watch to Monitoring. Tiers to Settings. Notifications to Settings. (#1436)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2022-01-24 09:56:16 -08:00
Cesar Celis Hernandez
0ba60bd674 Add test for listing buckets (#1440) 2022-01-24 11:27:32 -06:00
Lenin Alevski
41b34645f9 Subnet cluster registration (#1338)
- Removed old registration flow
- Add support for new online and offline cluster registration flow
- Support login accounts with mfa enabled
- Registration screens

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
2022-01-23 21:42:00 -08:00
Cesar Celis Hernandez
ceff2840d8 Add get user info test (#1438)
Co-authored-by: cniackz <cniackz@cniackzs-MacBook-Air.local>
Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-22 20:58:51 -08:00
Alex
6541938f16 Added TableRow customized style option (#1437) 2022-01-22 12:26:16 -08:00
Cesar Celis Hernandez
276eff4f15 Add list user test (#1435) 2022-01-21 21:54:36 -06:00
Daniel Valdivia
a3c9d0fe59 Loading component for suspense loaded screens (#1434)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-21 18:57:35 -06:00
Cesar Celis Hernandez
14f032971b Improving bucket tests in Console API (#1430) 2022-01-21 18:41:19 -06:00
Daniel Valdivia
9b9c54f775 Tweak some headers with the new names (#1433)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2022-01-21 15:57:43 -08:00
785 changed files with 18952 additions and 6696 deletions

View File

@@ -1,4 +1,4 @@
name: "Front-End Permission Tests"
name: "UI"
on:
pull_request:
@@ -10,7 +10,7 @@ on:
jobs:
permissions:
name: Front-End Permission Tests
name: Permissions Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -63,7 +63,7 @@ jobs:
- name: Run TestCafe Tests
uses: DevExpress/testcafe-action@latest
with:
args: '"chrome:headless" portal-ui/tests/permissions/ --skip-js-errors '
args: '"chrome:headless" portal-ui/tests/permissions/ --skip-js-errors -c 3'
- name: Clean up users & policies
run: |

View File

@@ -19,9 +19,17 @@ linters:
- unparam
- unused
- structcheck
- goheader
linters-settings:
goheader:
values:
regexp:
copyright-holder: Copyright \(c\) (20\d\d\-20\d\d)|2021|({{year}})
template-path: .license.tmpl
service:
golangci-lint-version: 1.27.0 # use the fixed version to not introduce new linters unexpectedly
golangci-lint-version: 1.43.0 # use the fixed version to not introduce new linters unexpectedly
issues:
exclude-use-default: false
@@ -36,3 +44,4 @@ issues:
run:
skip-dirs:
- pkg/clientgen
- pkg/apis/networking.gke.io

View File

@@ -13,7 +13,7 @@ release:
before:
hooks:
# you may remove this if you don't use vgo
- go mod tidy
- go mod tidy -compat=1.17
builds:
-

15
.license.tmpl Normal file
View File

@@ -0,0 +1,15 @@
This file is part of MinIO Console Server
{{copyright-holder}} MinIO, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -1,4 +1,4 @@
FROM node:14 as uilayer
FROM node:17 as uilayer
WORKDIR /app
@@ -12,7 +12,7 @@ RUN make build-static
USER node
FROM golang:1.16 as golayer
FROM golang:1.17 as golayer
RUN apt-get update -y && apt-get install -y ca-certificates
@@ -31,7 +31,7 @@ ENV CGO_ENABLED=0
COPY --from=uilayer /app/build /go/src/github.com/minio/console/portal-ui/build
RUN go build -ldflags "-w -s" -a -o console ./cmd/console
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.3
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
MAINTAINER MinIO Development "dev@min.io"
EXPOSE 9090

View File

@@ -1,4 +1,4 @@
FROM node:14 as uilayer
FROM node:17 as uilayer
WORKDIR /app

View File

@@ -1,4 +1,4 @@
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.5
ARG TAG

View File

@@ -20,7 +20,7 @@ k8sdev:
getdeps:
@mkdir -p ${GOPATH}/bin
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.40.1)
@which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.43.0)
verifiers: getdeps fmt lint

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
@@ -47,7 +47,7 @@ func (c *HTTPClient) Post(url, contentType string, body io.Reader) (resp *http.R
return c.Client.Post(url, contentType, body)
}
// Do implements http.Client.Do()
// Do implement http.Client.Do()
func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) {
return c.Client.Do(req)
}

View File

@@ -1,6 +1,3 @@
//go:build !operator
// +build !operator
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
@@ -17,6 +14,9 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build !operator
// +build !operator
package main
import (

View File

@@ -32,22 +32,22 @@ import (
// Help template for Console.
var consoleHelpTemplate = `NAME:
{{.Name}} - {{.Usage}}
{{.Name}} - {{.Usage}}
DESCRIPTION:
{{.Description}}
{{.Description}}
USAGE:
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}}{{end}} [ARGS...]
{{.HelpName}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}}{{end}} [ARGS...]
COMMANDS:
{{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .VisibleFlags}}
{{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
{{end}}{{if .VisibleFlags}}
FLAGS:
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
{{range .VisibleFlags}}{{.}}
{{end}}{{end}}
VERSION:
{{.Version}}
{{.Version}}
`
func newApp(name string) *cli.App {

View File

@@ -225,30 +225,5 @@ func startOperatorServer(ctx *cli.Context) error {
defer server.Shutdown()
// subnet license refresh process
go func() {
// start refreshing subnet license after 5 seconds..
time.Sleep(time.Second * 5)
failedAttempts := 0
for {
if err := operatorapi.RefreshLicense(); err != nil {
operatorapi.LogError("Refreshing subnet license failed: %v", err)
failedAttempts++
// end license refresh after 3 consecutive failed attempts
if failedAttempts >= 3 {
return
}
// wait 5 minutes and retry again
time.Sleep(time.Minute * 5)
continue
}
// if license refreshed successfully reset the counter
failedAttempts = 0
// try to refresh license every 24 hrs
time.Sleep(time.Hour * 24)
}
}()
return server.Serve()
}

63
go.mod
View File

@@ -6,9 +6,9 @@ require (
github.com/blang/semver/v4 v4.0.0
github.com/cheggaaa/pb/v3 v3.0.6
github.com/dustin/go-humanize v1.0.0
github.com/go-openapi/errors v0.20.1
github.com/go-openapi/errors v0.20.2
github.com/go-openapi/loads v0.21.0
github.com/go-openapi/runtime v0.21.0
github.com/go-openapi/runtime v0.21.1
github.com/go-openapi/spec v0.20.4
github.com/go-openapi/strfmt v0.21.1
github.com/go-openapi/swag v0.19.15
@@ -20,8 +20,8 @@ require (
github.com/minio/cli v1.22.0
github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf
github.com/minio/kes v0.11.0
github.com/minio/madmin-go v1.1.23
github.com/minio/mc v0.0.0-20211207230606-23a05f5a17f2
github.com/minio/madmin-go v1.3.0
github.com/minio/mc v0.0.0-20220204044644-e048c85d71a7
github.com/minio/minio-go/v7 v7.0.21
github.com/minio/operator v0.0.0-20220110040724-a5d59a342b7f
github.com/minio/pkg v1.1.14
@@ -30,9 +30,10 @@ require (
github.com/rs/xid v1.3.0
github.com/secure-io/sio-go v0.3.1
github.com/stretchr/testify v1.7.0
github.com/tidwall/gjson v1.12.1
github.com/unrolled/secure v1.0.9
golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.21.1
@@ -43,17 +44,19 @@ require (
require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
github.com/VividCortex/ewma v1.1.1 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/briandowns/spinner v1.16.0 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/charmbracelet/bubbles v0.10.0 // indirect
github.com/charmbracelet/bubbletea v0.19.3 // indirect
github.com/charmbracelet/lipgloss v0.4.1-0.20220204041308-bf2912e703f6 // indirect
github.com/cheggaaa/pb v1.0.29 // indirect
github.com/containerd/console v1.0.2 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
github.com/evanphx/json-patch v4.9.0+incompatible // indirect
@@ -61,17 +64,18 @@ require (
github.com/fatih/structs v1.1.0 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/analysis v0.21.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/goccy/go-json v0.7.9 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gnostic v0.5.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@@ -85,10 +89,12 @@ require (
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.0 // indirect
github.com/lestrrat-go/iter v1.0.1 // indirect
github.com/lestrrat-go/jwx v1.2.7 // indirect
github.com/lestrrat-go/jwx v1.2.14 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.10 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-ieproxy v0.0.1 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
@@ -102,46 +108,51 @@ require (
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/profile v1.6.0 // indirect
github.com/pkg/xattr v0.4.3 // indirect
github.com/pkg/xattr v0.4.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_golang v1.11.0 // indirect
github.com/prometheus/client_model v0.2.0 // indirect
github.com/prometheus/common v0.31.1 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rjeczalik/notify v0.9.2 // indirect
github.com/shirou/gopsutil/v3 v3.21.8 // indirect
github.com/shirou/gopsutil/v3 v3.21.12 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/tidwall/gjson v1.10.2 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tinylib/msgp v1.1.6 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect
go.etcd.io/etcd/client/v3 v3.5.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.etcd.io/etcd/api/v3 v3.5.1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.1 // indirect
go.etcd.io/etcd/client/v3 v3.5.1 // indirect
go.mongodb.org/mongo-driver v1.7.5 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 // indirect
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect
google.golang.org/appengine v1.6.6 // indirect
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 // indirect
google.golang.org/grpc v1.41.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect
google.golang.org/grpc v1.43.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/h2non/filetype.v1 v1.0.5 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/ini.v1 v1.66.3 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/klog/v2 v2.8.0 // indirect
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 // indirect

561
go.sum

File diff suppressed because it is too large Load Diff

15
hack/header.go.txt Normal file
View File

@@ -0,0 +1,15 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@@ -1,18 +1,4 @@
#!/bin/bash
#
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -o errexit
set -o nounset
@@ -20,10 +6,7 @@ set -o pipefail
SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/..
go get -d k8s.io/code-generator/...
# Checkout code-generator to compatible version
#(cd $GOPATH/src/k8s.io/code-generator && git checkout origin/release-1.14 -B release-1.14)
GO111MODULE=off go get -d k8s.io/code-generator/...
REPOSITORY=github.com/minio/console
$GOPATH/src/k8s.io/code-generator/generate-groups.sh all \

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,168 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package integration
import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"testing"
"time"
iampolicy "github.com/minio/pkg/iam/policy"
"github.com/stretchr/testify/assert"
)
func TestAddServiceAccount(t *testing.T) {
/*
This is an atomic API Test to add a user service account, the intention
is simple, add a user and make sure the response is 201 meaning that the
user got added successfully.
After test completion, it is expected that user is removed, so other
tests like users.ts can run over clean data and we don't collide against
it.
*/
assert := assert.New(t)
client := &http.Client{
Timeout: 3 * time.Second,
}
// Add service account
fmt.Println(".......................TestServiceAccountPolicy(): Create Data to add user")
requestDataAddServiceAccount := map[string]interface{}{
"accessKey": "testuser1",
"secretKey": "password",
"policy": "{" +
"\n \"Version\": \"2012-10-17\"," +
"\n \"Statement\": [" +
"\n {" +
"\n \"Effect\": \"Allow\"," +
"\n \"Action\": [" +
"\n \"s3:GetBucketLocation\"," +
"\n \"s3:GetObject\"" +
"\n ]," +
"\n \"Resource\": [" +
"\n \"arn:aws:s3:::*\"" +
"\n ]" +
"\n }" +
"\n ]" +
"\n}",
}
fmt.Println("..............................TestServiceAccountPolicy(): Prepare the POST")
requestDataJSON, _ := json.Marshal(requestDataAddServiceAccount)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"POST", "http://localhost:9090/api/v1/service-account-credentials", requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
fmt.Println(".................................TestServiceAccountPolicy(): Make the POST")
response, err := client.Do(request)
if err != nil {
log.Println(err)
return
}
fmt.Println("..................................TestServiceAccountPolicy(): Verification")
fmt.Println(".................................TestServiceAccountPolicy(): POST response")
fmt.Println(response)
fmt.Println("....................................TestServiceAccountPolicy(): POST error")
fmt.Println(err)
if response != nil {
fmt.Println("POST StatusCode:", response.StatusCode)
assert.Equal(201, response.StatusCode, "Status Code is incorrect")
}
fmt.Println("...................................TestServiceAccountPolicy(): Remove user")
// Test policy
fmt.Println(".......................TestAddUserServiceAccount(): Create Data to add user")
request, err = http.NewRequest(
"GET", "http://localhost:9090/api/v1/service-accounts/testuser1/policy", nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
fmt.Println(".................................TestAddServiceAccount(): Make the POST")
response, err = client.Do(request)
if err != nil {
log.Println(err)
return
}
fmt.Println("..................................TestAddServiceAccount(): Verification")
fmt.Println(".................................TestAddServiceAccount(): POST response")
fmt.Println(response)
fmt.Println("....................................TestAddServiceAccount(): POST error")
fmt.Println(err)
if response != nil {
fmt.Println("POST StatusCode:", response.StatusCode)
assert.Equal(200, response.StatusCode, "Status Code is incorrect")
buf := new(bytes.Buffer)
buf.ReadFrom(response.Body)
var actual *iampolicy.Policy
var expected *iampolicy.Policy
json.Unmarshal(buf.Bytes(), actual)
policy, err := json.Marshal(requestDataAddServiceAccount["policy"])
if err != nil {
log.Println(err)
return
}
json.Unmarshal(policy, expected)
assert.Equal(expected, actual)
}
fmt.Println("...................................TestServiceAccountPolicy(): Remove service account")
// {{baseUrl}}/user?name=proident velit
// Investiga como se borra en el browser.
request, err = http.NewRequest(
"DELETE", "http://localhost:9090/api/v1/service-accounts/testuser1", nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
fmt.Println("...............................TestServiceAccountPolicy(): Make the DELETE")
response, err = client.Do(request)
if err != nil {
log.Println(err)
return
}
fmt.Println("..................................TestServiceAccountPolicy(): Verification")
fmt.Println("...............................TestServiceAccountPolicy(): DELETE response")
fmt.Println(response)
fmt.Println("..................................TestServiceAccountPolicy(): DELETE error")
fmt.Println(err)
if response != nil {
fmt.Println("DELETE StatusCode:", response.StatusCode)
assert.Equal(204, response.StatusCode, "has to be 204 when delete user")
}
}

View File

@@ -20,39 +20,32 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestAddUser(t *testing.T) {
func AddUser(accessKey string, secretKey string, groups []string, policies []string) (*http.Response, error) {
/*
This is an atomic API Test to add a user via api/v1/users, the intention
is simple, add a user and make sure the response is 201 meaning that the
user got added successfully.
After test completion, it is expected that user is removed, so other
tests like users.ts can run over clean data and we don't collide against
it.
This is an atomic function to add user and can be reused across
different functions.
*/
assert := assert.New(t)
client := &http.Client{
Timeout: 3 * time.Second,
}
var x [0]string
fmt.Println(x)
fmt.Println(".......................TestAddUser(): Create Data to add user")
requestDataAdd := map[string]interface{}{
"accessKey": "accessKey",
"secretKey": "secretKey",
"groups": x,
"policies": x,
"accessKey": accessKey,
"secretKey": secretKey,
"groups": groups,
"policies": policies,
}
fmt.Println("..............................TestAddUser(): Prepare the POST")
@@ -62,13 +55,302 @@ func TestAddUser(t *testing.T) {
"POST", "http://localhost:9090/api/v1/users", requestDataBody)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
fmt.Println(".................................TestAddUser(): Make the POST")
response, err := client.Do(request)
return response, err
}
func DeleteUser(userName string) (*http.Response, error) {
/*
This is an atomic function to delete user and can be reused across
different functions.
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
fmt.Println("...................................TestAddUser(): Remove user")
request, err := http.NewRequest(
"DELETE", "http://localhost:9090/api/v1/user?name="+userName, nil)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
fmt.Println("...............................TestAddUser(): Make the DELETE")
response, err := client.Do(request)
return response, err
}
func ListUsers(offset string, limit string) (*http.Response, error) {
/*
This is an atomic function to list users.
{{baseUrl}}/users?offset=-5480083&limit=-5480083
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
fmt.Println("...................................TestAddUser(): Remove user")
request, err := http.NewRequest(
"GET",
"http://localhost:9090/api/v1/users?offset="+offset+"&limit="+limit,
nil)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
fmt.Println("...............................TestAddUser(): Make the DELETE")
response, err := client.Do(request)
return response, err
}
func GetUserInformation(userName string) (*http.Response, error) {
/*
Helper function to get user information via API:
{{baseUrl}}/user?name=proident velit
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"GET",
"http://localhost:9090/api/v1/user?name="+userName,
nil)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func UpdateUserInformation(name string, status string, groups []string) (*http.Response, error) {
/*
Helper function to update user information:
PUT: {{baseUrl}}/user?name=proident velit
Body:
{
"status": "nisi voluptate amet ea",
"groups": [
"ipsum eu cupidatat",
"aliquip non nulla"
]
}
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataAdd := map[string]interface{}{
"status": status,
"groups": groups,
}
requestDataJSON, _ := json.Marshal(requestDataAdd)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"PUT", "http://localhost:9090/api/v1/user?name="+name, requestDataBody)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func RemoveUser(name string) (*http.Response, error) {
/*
Helper function to remove user.
DELETE: {{baseUrl}}/user?name=proident velit
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"DELETE", "http://localhost:9090/api/v1/user?name="+name, nil)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func UpdateGroupsForAUser(userName string, groups []string) (*http.Response, error) {
/*
Helper function to update groups for a user
PUT: {{baseUrl}}/user/groups?name=username
{
"groups":[
"groupone",
"grouptwo"
]
}
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataAdd := map[string]interface{}{
"groups": groups,
}
requestDataJSON, _ := json.Marshal(requestDataAdd)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"PUT",
"http://localhost:9090/api/v1/user/groups?name="+userName,
requestDataBody,
)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func CreateServiceAccountForUser(userName string, policy string) (*http.Response, error) {
/*
Helper function to Create Service Account for user
POST: api/v1/user/username/service-accounts
{
"policy": "ad magna"
}
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataAdd := map[string]interface{}{
"policy": policy,
}
requestDataJSON, _ := json.Marshal(requestDataAdd)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"POST",
"http://localhost:9090/api/v1/user/"+userName+"/service-accounts",
requestDataBody,
)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func ReturnsAListOfServiceAccountsForAUser(userName string) (*http.Response, error) {
/*
Helper function to return a list of service accounts for a user.
GET: {{baseUrl}}/user/:name/service-accounts
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
request, err := http.NewRequest(
"GET",
"http://localhost:9090/api/v1/user/"+userName+"/service-accounts",
nil,
)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func AddGroup(group string, members []string) (*http.Response, error) {
/*
Helper function to add a group.
*/
client := &http.Client{
Timeout: 3 * time.Second,
}
requestDataAdd := map[string]interface{}{
"group": group,
"members": members,
}
requestDataJSON, _ := json.Marshal(requestDataAdd)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"POST",
"http://localhost:9090/api/v1/groups",
requestDataBody,
)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
response, err := client.Do(request)
return response, err
}
func UsersGroupsBulk(users []string, groups []string) (*http.Response, error) {
/*
Helper function to test Bulk functionality to Add Users to Groups.
PUT: {{baseUrl}}/users-groups-bulk
{
"users": [
"magna id",
"enim sit tempor incididunt"
],
"groups": [
"nisi est esse",
"fugiat eu"
]
}
*/
requestDataAdd := map[string]interface{}{
"users": users,
"groups": groups,
}
requestDataJSON, _ := json.Marshal(requestDataAdd)
requestDataBody := bytes.NewReader(requestDataJSON)
request, err := http.NewRequest(
"PUT",
"http://localhost:9090/api/v1/users-groups-bulk",
requestDataBody,
)
if err != nil {
log.Println(err)
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
client := &http.Client{
Timeout: 2 * time.Second,
}
response, err := client.Do(request)
return response, err
}
func TestAddUser(t *testing.T) {
/*
This is an API Test to add a user via api/v1/users, the intention
is simple, add a user and make sure the response is 201 meaning that the
user got added successfully.
After test completion, it is expected that user is removed, so other
tests like users.ts can run over clean data and we don't collide against
it.
*/
assert := assert.New(t)
// With no groups & no policies
var groups = []string{}
var policies = []string{}
fmt.Println(".................................TestAddUser(): Make the POST")
response, err := AddUser("accessKey", "secretKey", groups, policies)
if err != nil {
log.Println(err)
return
@@ -83,20 +365,7 @@ func TestAddUser(t *testing.T) {
assert.Equal(201, response.StatusCode, "Status Code is incorrect")
}
fmt.Println("...................................TestAddUser(): Remove user")
// {{baseUrl}}/user?name=proident velit
// Investiga como se borra en el browser.
request, err = http.NewRequest(
"DELETE", "http://localhost:9090/api/v1/user?name=accessKey", nil)
if err != nil {
log.Println(err)
return
}
request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
request.Header.Add("Content-Type", "application/json")
fmt.Println("...............................TestAddUser(): Make the DELETE")
response, err = client.Do(request)
response, err = DeleteUser("accessKey")
if err != nil {
log.Println(err)
return
@@ -112,3 +381,476 @@ func TestAddUser(t *testing.T) {
}
}
func TestListUsers(t *testing.T) {
/*
This test is intended to list users via API.
1. First, it creates the users
2. Then, it lists the users <------ 200 is expected when listing them.
3. Finally, it deletes the users
*/
assert := assert.New(t)
// With no groups & no policies
var groups = []string{}
var policies = []string{}
// 1. Create the users
numberOfUsers := 5
for i := 1; i < numberOfUsers; i++ {
fmt.Println("............................TestListUsers(): Adding users")
fmt.Println(strconv.Itoa(i) + "accessKey" + strconv.Itoa(i))
response, err := AddUser(
strconv.Itoa(i)+"accessKey"+strconv.Itoa(i),
"secretKey"+strconv.Itoa(i), groups, policies)
if err != nil {
log.Println(err)
return
}
fmt.Println("............................TestListUsers(): Verification")
fmt.Println("...........................TestListUsers(): POST response")
fmt.Println(response)
fmt.Println("..............................TestListUsers(): POST error")
fmt.Println(err)
if response != nil {
fmt.Println("POST StatusCode:", response.StatusCode)
assert.Equal(201, response.StatusCode,
"Status Code is incorrect on index: "+strconv.Itoa(i))
}
b, err := io.ReadAll(response.Body)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(b))
}
// 2. List the users
listResponse, listError := ListUsers("-5480083", "-5480083")
if listError != nil {
log.Fatalln(listError)
}
if listResponse != nil {
fmt.Println("POST StatusCode:", listResponse.StatusCode)
assert.Equal(200, listResponse.StatusCode,
"TestListUsers(): Status Code is incorrect when listing users")
}
b, err := io.ReadAll(listResponse.Body)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(b))
// 3. Delete the users
for i := 1; i < numberOfUsers; i++ {
response, err := DeleteUser(
strconv.Itoa(i) + "accessKey" + strconv.Itoa(i))
if err != nil {
log.Println(err)
return
}
fmt.Println("............................TestListUsers(): Verification")
fmt.Println(".........................TestListUsers(): DELETE response")
fmt.Println(response)
fmt.Println("............................TestListUsers(): DELETE error")
fmt.Println(err)
if response != nil {
fmt.Println("DELETE StatusCode:", response.StatusCode)
assert.Equal(204,
response.StatusCode, "has to be 204 when delete user")
}
}
}
func TestGetUserInfo(t *testing.T) {
/*
Test to get the user information via API.
*/
// 1. Create the user
fmt.Println("TestGetUserInfo(): 1. Create the user")
assert := assert.New(t)
var groups = []string{}
var policies = []string{}
response, err := AddUser("accessKey", "secretKey", groups, policies)
if err != nil {
log.Println(err)
return
}
if response != nil {
fmt.Println("POST StatusCode:", response.StatusCode)
assert.Equal(201, response.StatusCode, "Status Code is incorrect")
}
// 2. Get user information
fmt.Println("TestGetUserInfo(): 2. Get user information")
response, err = GetUserInformation("accessKey")
if err != nil {
log.Println(err)
assert.Fail("There was an error in the response")
return
}
// 3. Verify user information
fmt.Println("TestGetUserInfo(): 3. Verify user information")
if response != nil {
fmt.Println("POST StatusCode:", response.StatusCode)
assert.Equal(200, response.StatusCode, "Status Code is incorrect")
}
b, err := io.ReadAll(response.Body)
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(b))
expected := "{\"accessKey\":\"accessKey\",\"memberOf\":null,\"policy\":[],\"status\":\"enabled\"}\n"
obtained := string(b)
assert.Equal(expected, obtained, "User Information is wrong")
}
func TestUpdateUserInfoSuccessfulResponse(t *testing.T) {
/*
Update User Information Test with Successful Response
*/
assert := assert.New(t)
// 1. Create an active user
var groups = []string{}
var policies = []string{}
addUserResponse, addUserError := AddUser(
"updateuser", "secretKey", groups, policies)
if addUserError != nil {
log.Println(addUserError)
return
}
if addUserResponse != nil {
fmt.Println("StatusCode:", addUserResponse.StatusCode)
assert.Equal(
201, addUserResponse.StatusCode, "Status Code is incorrect")
}
// 2. Deactivate the user
// '{"status":"disabled","groups":[]}'
updateUserResponse, UpdateUserError := UpdateUserInformation(
"updateuser", "disabled", groups)
// 3. Verify user got deactivated
if UpdateUserError != nil {
log.Println(UpdateUserError)
return
}
if updateUserResponse != nil {
fmt.Println("StatusCode:", updateUserResponse.StatusCode)
assert.Equal(
200, updateUserResponse.StatusCode, "Status Code is incorrect")
}
b, err := io.ReadAll(updateUserResponse.Body)
if err != nil {
log.Fatalln(err)
}
assert.True(strings.Contains(string(b), "disabled"))
}
func TestUpdateUserInfoGenericErrorResponse(t *testing.T) {
/*
Update User Information Test with Generic Error Response
*/
assert := assert.New(t)
// 1. Create an active user
var groups = []string{}
var policies = []string{}
addUserResponse, addUserError := AddUser(
"updateusererror", "secretKey", groups, policies)
if addUserError != nil {
log.Println(addUserError)
return
}
if addUserResponse != nil {
fmt.Println("StatusCode:", addUserResponse.StatusCode)
assert.Equal(
201, addUserResponse.StatusCode, "Status Code is incorrect")
}
// 2. Deactivate the user with wrong status
updateUserResponse, UpdateUserError := UpdateUserInformation(
"updateusererror", "inactive", groups)
// 3. Verify user got deactivated
if UpdateUserError != nil {
log.Println(UpdateUserError)
assert.Fail("There was an error while updating user info")
return
}
if updateUserResponse != nil {
fmt.Println("StatusCode:", updateUserResponse.StatusCode)
assert.Equal(
500, updateUserResponse.StatusCode, "Status Code is incorrect")
}
b, err := io.ReadAll(updateUserResponse.Body)
if err != nil {
log.Fatalln(err)
}
assert.True(strings.Contains(string(b), "status not valid"))
}
func TestRemoveUserSuccessfulResponse(t *testing.T) {
/*
To test removing a user from API
*/
assert := assert.New(t)
// 1. Create an active user
var groups = []string{}
var policies = []string{}
addUserResponse, addUserError := AddUser(
"testremoveuser1", "secretKey", groups, policies)
if addUserError != nil {
log.Println(addUserError)
return
}
if addUserResponse != nil {
fmt.Println("StatusCode:", addUserResponse.StatusCode)
assert.Equal(
201, addUserResponse.StatusCode, "Status Code is incorrect")
}
// 2. Remove the user
removeUserResponse, removeUserError := RemoveUser("testremoveuser1")
if removeUserError != nil {
log.Println(removeUserError)
return
}
if removeUserResponse != nil {
fmt.Println("StatusCode:", removeUserResponse.StatusCode)
assert.Equal(
204, removeUserResponse.StatusCode, "Status Code is incorrect")
}
// 3. Verify the user got removed
getUserInfoResponse, getUserInfoError := GetUserInformation(
"testremoveuser1")
if getUserInfoError != nil {
log.Println(getUserInfoError)
assert.Fail("There was an error in the response")
return
}
if getUserInfoResponse != nil {
fmt.Println("StatusCode:", getUserInfoResponse.StatusCode)
assert.Equal(
404, getUserInfoResponse.StatusCode, "Status Code is incorrect")
}
finalResponse := inspectHTTPResponse(getUserInfoResponse)
printMessage(finalResponse)
assert.True(strings.Contains(
finalResponse, "The specified user does not exist"), finalResponse)
}
func TestUpdateGroupsForAUser(t *testing.T) {
/*
To test Update Groups For a User End Point.
*/
// 1. Create the user
numberOfGroups := 3
groupName := "updategroupforausergroup"
userName := "updategroupsforauser1"
assert := assert.New(t)
var groups = []string{}
var policies = []string{}
response, err := AddUser(userName, "secretKey", groups, policies)
if err != nil {
log.Println(err)
return
}
if response != nil {
fmt.Println("StatusCode:", response.StatusCode)
assert.Equal(201, response.StatusCode, "Status Code is incorrect")
}
// 2. Update the groups of the created user with newGroups
var newGroups = make([]string, 3)
for i := 0; i < numberOfGroups; i++ {
newGroups[i] = groupName + strconv.Itoa(i)
}
response, err = UpdateGroupsForAUser(userName, newGroups)
if err != nil {
log.Println(err)
return
}
if response != nil {
fmt.Println("StatusCode:", response.StatusCode)
assert.Equal(200, response.StatusCode, "Status Code is incorrect")
}
// 3. Verify the newGroups were updated accordingly
getUserInfoResponse, getUserInfoErr := GetUserInformation(userName)
if getUserInfoErr != nil {
log.Println(getUserInfoErr)
assert.Fail("There was an error in the response")
return
}
if getUserInfoResponse != nil {
fmt.Println("StatusCode:", getUserInfoResponse.StatusCode)
assert.Equal(
200, getUserInfoResponse.StatusCode, "Status Code is incorrect")
}
finalResponse := inspectHTTPResponse(getUserInfoResponse)
for i := 0; i < numberOfGroups; i++ {
assert.True(strings.Contains(
finalResponse, groupName+strconv.Itoa(i)), finalResponse)
}
}
func TestCreateServiceAccountForUser(t *testing.T) {
/*
To test creation of service account for a user.
*/
// Test's variables
userName := "testcreateserviceaccountforuser1"
assert := assert.New(t)
policy := ""
serviceAccountLengthInBytes := 40 // As observed, update as needed
// 1. Create the user
var groups = []string{}
var policies = []string{}
response, err := AddUser(userName, "secretKey", groups, policies)
if err != nil {
log.Println(err)
return
}
if response != nil {
fmt.Println("StatusCode:", response.StatusCode)
assert.Equal(201, response.StatusCode, "Status Code is incorrect")
}
// 2. Create the service account for the user
createServiceAccountResponse,
createServiceAccountError := CreateServiceAccountForUser(
userName,
policy,
)
if createServiceAccountError != nil {
log.Println(createServiceAccountError)
assert.Fail("Error in createServiceAccountError")
}
if createServiceAccountResponse != nil {
fmt.Println("StatusCode:", createServiceAccountResponse.StatusCode)
assert.Equal(
201, createServiceAccountResponse.StatusCode,
inspectHTTPResponse(createServiceAccountResponse),
)
}
// 3. Verify the service account for the user
listOfAccountsResponse,
listOfAccountsError := ReturnsAListOfServiceAccountsForAUser(userName)
if listOfAccountsError != nil {
log.Println(listOfAccountsError)
assert.Fail("Error in listOfAccountsError")
}
finalResponse := inspectHTTPResponse(listOfAccountsResponse)
if listOfAccountsResponse != nil {
fmt.Println("StatusCode:", listOfAccountsResponse.StatusCode)
assert.Equal(
200, listOfAccountsResponse.StatusCode,
finalResponse,
)
}
assert.Equal(len(finalResponse), serviceAccountLengthInBytes, finalResponse)
}
func TestUsersGroupsBulk(t *testing.T) {
/*
To test UsersGroupsBulk End Point
*/
// Vars
assert := assert.New(t)
numberOfUsers := 5
numberOfGroups := 1
//var groups = []string{}
var policies = []string{}
username := "testusersgroupbulk"
groupName := "testusersgroupsbulkgroupone"
var members = []string{}
users := make([]string, numberOfUsers)
groups := make([]string, numberOfGroups)
// 1. Create some users
for i := 0; i < numberOfUsers; i++ {
users[i] = username + strconv.Itoa(i)
response, err := AddUser(
users[i],
"secretKey"+strconv.Itoa(i), []string{}, policies)
if err != nil {
log.Println(err)
return
}
if response != nil {
fmt.Println("POST StatusCode:", response.StatusCode)
assert.Equal(201, response.StatusCode,
"Status Code is incorrect on index: "+strconv.Itoa(i))
}
}
// 2. Create a group with no members
responseAddGroup, errorAddGroup := AddGroup(groupName, members)
if errorAddGroup != nil {
log.Println(errorAddGroup)
return
}
finalResponse := inspectHTTPResponse(responseAddGroup)
if responseAddGroup != nil {
fmt.Println("POST StatusCode:", responseAddGroup.StatusCode)
assert.Equal(
201,
responseAddGroup.StatusCode,
finalResponse,
)
}
// 3. Add users to the group
groups[0] = groupName
responseUsersGroupsBulk, errorUsersGroupsBulk := UsersGroupsBulk(
users,
groups,
)
if errorUsersGroupsBulk != nil {
log.Println(errorUsersGroupsBulk)
return
}
finalResponse = inspectHTTPResponse(responseUsersGroupsBulk)
if responseUsersGroupsBulk != nil {
fmt.Println("POST StatusCode:", responseUsersGroupsBulk.StatusCode)
assert.Equal(
200,
responseUsersGroupsBulk.StatusCode,
finalResponse,
)
}
// 4. Verify users got added to the group
for i := 0; i < numberOfUsers; i++ {
responseGetUserInfo, errGetUserInfo := GetUserInformation(
username + strconv.Itoa(i),
)
if errGetUserInfo != nil {
log.Println(errGetUserInfo)
assert.Fail("There was an error in the response")
return
}
finalResponse = inspectHTTPResponse(responseGetUserInfo)
if responseGetUserInfo != nil {
assert.Equal(200, responseGetUserInfo.StatusCode, finalResponse)
}
// Make sure the user belongs to the created group
assert.True(strings.Contains(string(finalResponse), groupName))
}
}

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.13.3
image: minio/console:v0.14.3
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE

View File

@@ -32,7 +32,7 @@ spec:
spec:
containers:
- name: console
image: minio/console:v0.13.3
image: minio/console:v0.14.3
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_MINIO_SERVER

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -362,7 +362,7 @@ type BucketDetailsQuota struct {
Quota int64 `json:"quota,omitempty"`
// type
// Enum: [fifo hard]
// Enum: [hard]
Type string `json:"type,omitempty"`
}
@@ -384,7 +384,7 @@ var bucketDetailsQuotaTypeTypePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["fifo","hard"]`), &res); err != nil {
if err := json.Unmarshal([]byte(`["hard"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
@@ -394,9 +394,6 @@ func init() {
const (
// BucketDetailsQuotaTypeFifo captures enum value "fifo"
BucketDetailsQuotaTypeFifo string = "fifo"
// BucketDetailsQuotaTypeHard captures enum value "hard"
BucketDetailsQuotaTypeHard string = "hard"
)

View File

@@ -37,8 +37,12 @@ import (
type BucketAccess string
func NewBucketAccess(value BucketAccess) *BucketAccess {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated BucketAccess.
func (m BucketAccess) Pointer() *BucketAccess {
return &m
}
const (

View File

@@ -37,8 +37,12 @@ import (
type BucketEncryptionType string
func NewBucketEncryptionType(value BucketEncryptionType) *BucketEncryptionType {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated BucketEncryptionType.
func (m BucketEncryptionType) Pointer() *BucketEncryptionType {
return &m
}
const (

View File

@@ -41,7 +41,7 @@ type BucketQuota struct {
Quota int64 `json:"quota,omitempty"`
// type
// Enum: [hard fifo]
// Enum: [hard]
Type string `json:"type,omitempty"`
}
@@ -63,7 +63,7 @@ var bucketQuotaTypeTypePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["hard","fifo"]`), &res); err != nil {
if err := json.Unmarshal([]byte(`["hard"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
@@ -75,9 +75,6 @@ const (
// BucketQuotaTypeHard captures enum value "hard"
BucketQuotaTypeHard string = "hard"
// BucketQuotaTypeFifo captures enum value "fifo"
BucketQuotaTypeFifo string = "fifo"
)
// prop value enum

View File

@@ -37,8 +37,12 @@ import (
type NofiticationService string
func NewNofiticationService(value NofiticationService) *NofiticationService {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated NofiticationService.
func (m NofiticationService) Pointer() *NofiticationService {
return &m
}
const (

View File

@@ -37,8 +37,12 @@ import (
type NotificationEventType string
func NewNotificationEventType(value NotificationEventType) *NotificationEventType {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated NotificationEventType.
func (m NotificationEventType) Pointer() *NotificationEventType {
return &m
}
const (

View File

@@ -37,8 +37,12 @@ import (
type ObjectLegalHoldStatus string
func NewObjectLegalHoldStatus(value ObjectLegalHoldStatus) *ObjectLegalHoldStatus {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated ObjectLegalHoldStatus.
func (m ObjectLegalHoldStatus) Pointer() *ObjectLegalHoldStatus {
return &m
}
const (

View File

@@ -37,8 +37,12 @@ import (
type ObjectRetentionMode string
func NewObjectRetentionMode(value ObjectRetentionMode) *ObjectRetentionMode {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated ObjectRetentionMode.
func (m ObjectRetentionMode) Pointer() *ObjectRetentionMode {
return &m
}
const (

View File

@@ -37,8 +37,12 @@ import (
type ObjectRetentionUnit string
func NewObjectRetentionUnit(value ObjectRetentionUnit) *ObjectRetentionUnit {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated ObjectRetentionUnit.
func (m ObjectRetentionUnit) Pointer() *ObjectRetentionUnit {
return &m
}
const (

View File

@@ -37,8 +37,12 @@ import (
type PolicyEntity string
func NewPolicyEntity(value PolicyEntity) *PolicyEntity {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated PolicyEntity.
func (m PolicyEntity) Pointer() *PolicyEntity {
return &m
}
const (

View File

@@ -37,8 +37,12 @@ import (
type ProfilerType string
func NewProfilerType(value ProfilerType) *ProfilerType {
v := value
return &v
return &value
}
// Pointer returns a pointer to a freshly-allocated ProfilerType.
func (m ProfilerType) Pointer() *ProfilerType {
return &m
}
const (

View File

@@ -45,7 +45,7 @@ type SetBucketQuota struct {
Enabled *bool `json:"enabled"`
// quota type
// Enum: [fifo hard]
// Enum: [hard]
QuotaType string `json:"quota_type,omitempty"`
}
@@ -80,7 +80,7 @@ var setBucketQuotaTypeQuotaTypePropEnum []interface{}
func init() {
var res []string
if err := json.Unmarshal([]byte(`["fifo","hard"]`), &res); err != nil {
if err := json.Unmarshal([]byte(`["hard"]`), &res); err != nil {
panic(err)
}
for _, v := range res {
@@ -90,9 +90,6 @@ func init() {
const (
// SetBucketQuotaQuotaTypeFifo captures enum value "fifo"
SetBucketQuotaQuotaTypeFifo string = "fifo"
// SetBucketQuotaQuotaTypeHard captures enum value "hard"
SetBucketQuotaQuotaTypeHard string = "hard"
)

View File

@@ -0,0 +1,122 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// SubnetLoginMFARequest subnet login m f a request
//
// swagger:model subnetLoginMFARequest
type SubnetLoginMFARequest struct {
// mfa token
// Required: true
MfaToken *string `json:"mfa_token"`
// otp
// Required: true
Otp *string `json:"otp"`
// username
// Required: true
Username *string `json:"username"`
}
// Validate validates this subnet login m f a request
func (m *SubnetLoginMFARequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateMfaToken(formats); err != nil {
res = append(res, err)
}
if err := m.validateOtp(formats); err != nil {
res = append(res, err)
}
if err := m.validateUsername(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetLoginMFARequest) validateMfaToken(formats strfmt.Registry) error {
if err := validate.Required("mfa_token", "body", m.MfaToken); err != nil {
return err
}
return nil
}
func (m *SubnetLoginMFARequest) validateOtp(formats strfmt.Registry) error {
if err := validate.Required("otp", "body", m.Otp); err != nil {
return err
}
return nil
}
func (m *SubnetLoginMFARequest) validateUsername(formats strfmt.Registry) error {
if err := validate.Required("username", "body", m.Username); err != nil {
return err
}
return nil
}
// ContextValidate validates this subnet login m f a request based on context it is used
func (m *SubnetLoginMFARequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetLoginMFARequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetLoginMFARequest) UnmarshalBinary(b []byte) error {
var res SubnetLoginMFARequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,73 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetLoginRequest subnet login request
//
// swagger:model subnetLoginRequest
type SubnetLoginRequest struct {
// api key
APIKey string `json:"apiKey,omitempty"`
// password
Password string `json:"password,omitempty"`
// username
Username string `json:"username,omitempty"`
}
// Validate validates this subnet login request
func (m *SubnetLoginRequest) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this subnet login request based on context it is used
func (m *SubnetLoginRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetLoginRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetLoginRequest) UnmarshalBinary(b []byte) error {
var res SubnetLoginRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,142 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetLoginResponse subnet login response
//
// swagger:model subnetLoginResponse
type SubnetLoginResponse struct {
// access token
AccessToken string `json:"access_token,omitempty"`
// mfa token
MfaToken string `json:"mfa_token,omitempty"`
// organizations
Organizations []*SubnetOrganization `json:"organizations"`
// registered
Registered bool `json:"registered,omitempty"`
}
// Validate validates this subnet login response
func (m *SubnetLoginResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateOrganizations(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetLoginResponse) validateOrganizations(formats strfmt.Registry) error {
if swag.IsZero(m.Organizations) { // not required
return nil
}
for i := 0; i < len(m.Organizations); i++ {
if swag.IsZero(m.Organizations[i]) { // not required
continue
}
if m.Organizations[i] != nil {
if err := m.Organizations[i].Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("organizations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("organizations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// ContextValidate validate this subnet login response based on the context it is used
func (m *SubnetLoginResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidateOrganizations(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetLoginResponse) contextValidateOrganizations(ctx context.Context, formats strfmt.Registry) error {
for i := 0; i < len(m.Organizations); i++ {
if m.Organizations[i] != nil {
if err := m.Organizations[i].ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("organizations" + "." + strconv.Itoa(i))
} else if ce, ok := err.(*errors.CompositeError); ok {
return ce.ValidateName("organizations" + "." + strconv.Itoa(i))
}
return err
}
}
}
return nil
}
// MarshalBinary interface implementation
func (m *SubnetLoginResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetLoginResponse) UnmarshalBinary(b []byte) error {
var res SubnetLoginResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,82 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetOrganization subnet organization
//
// swagger:model subnetOrganization
type SubnetOrganization struct {
// account Id
AccountID int64 `json:"accountId,omitempty"`
// company
Company string `json:"company,omitempty"`
// is account owner
IsAccountOwner bool `json:"isAccountOwner,omitempty"`
// short name
ShortName string `json:"shortName,omitempty"`
// subscription status
SubscriptionStatus string `json:"subscriptionStatus,omitempty"`
// user Id
UserID int64 `json:"userId,omitempty"`
}
// Validate validates this subnet organization
func (m *SubnetOrganization) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this subnet organization based on context it is used
func (m *SubnetOrganization) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetOrganization) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetOrganization) UnmarshalBinary(b []byte) error {
var res SubnetOrganization
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,67 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
)
// SubnetRegTokenResponse subnet reg token response
//
// swagger:model SubnetRegTokenResponse
type SubnetRegTokenResponse struct {
// reg token
RegToken string `json:"regToken,omitempty"`
}
// Validate validates this subnet reg token response
func (m *SubnetRegTokenResponse) Validate(formats strfmt.Registry) error {
return nil
}
// ContextValidate validates this subnet reg token response based on context it is used
func (m *SubnetRegTokenResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetRegTokenResponse) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetRegTokenResponse) UnmarshalBinary(b []byte) error {
var res SubnetRegTokenResponse
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -0,0 +1,105 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package models
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"context"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/swag"
"github.com/go-openapi/validate"
)
// SubnetRegisterRequest subnet register request
//
// swagger:model subnetRegisterRequest
type SubnetRegisterRequest struct {
// account id
// Required: true
AccountID *string `json:"account_id"`
// token
// Required: true
Token *string `json:"token"`
}
// Validate validates this subnet register request
func (m *SubnetRegisterRequest) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validateAccountID(formats); err != nil {
res = append(res, err)
}
if err := m.validateToken(formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SubnetRegisterRequest) validateAccountID(formats strfmt.Registry) error {
if err := validate.Required("account_id", "body", m.AccountID); err != nil {
return err
}
return nil
}
func (m *SubnetRegisterRequest) validateToken(formats strfmt.Registry) error {
if err := validate.Required("token", "body", m.Token); err != nil {
return err
}
return nil
}
// ContextValidate validates this subnet register request based on context it is used
func (m *SubnetRegisterRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
return nil
}
// MarshalBinary interface implementation
func (m *SubnetRegisterRequest) MarshalBinary() ([]byte, error) {
if m == nil {
return nil, nil
}
return swag.WriteJSON(m)
}
// UnmarshalBinary interface implementation
func (m *SubnetRegisterRequest) UnmarshalBinary(b []byte) error {
var res SubnetRegisterRequest
if err := swag.ReadJSON(b, &res); err != nil {
return err
}
*m = res
return nil
}

View File

@@ -66,6 +66,18 @@ type TenantLogs struct {
// labels
Labels []*Label `json:"labels"`
// log CPU request
LogCPURequest string `json:"logCPURequest,omitempty"`
// log d b CPU request
LogDBCPURequest string `json:"logDBCPURequest,omitempty"`
// log d b mem request
LogDBMemRequest string `json:"logDBMemRequest,omitempty"`
// log mem request
LogMemRequest string `json:"logMemRequest,omitempty"`
// node selector
NodeSelector []*NodeSelector `json:"nodeSelector"`

View File

@@ -1,3 +1,19 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package auth
import (

View File

@@ -1,5 +1,3 @@
// This file is safe to edit. Once it exists it will not be overwritten
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
@@ -94,8 +92,6 @@ func configureAPI(api *operations.OperatorAPI) http.Handler {
registerVolumesHandlers(api)
// Namespaces handlers
registerNamespaceHandlers(api)
// Subscription handlers
registerSubscriptionHandlers(api)
api.PreServerShutdown = func() {}

View File

@@ -1261,6 +1261,49 @@ func init() {
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/pvcs/{PVCName}/events": {
"get": {
"tags": [
"OperatorAPI"
],
"summary": "Get Events for PVC",
"operationId": "GetPVCEvents",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
},
{
"type": "string",
"name": "PVCName",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/eventListWrapper"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/security": {
"get": {
"tags": [
@@ -3325,6 +3368,18 @@ func init() {
"$ref": "#/definitions/label"
}
},
"logCPURequest": {
"type": "string"
},
"logDBCPURequest": {
"type": "string"
},
"logDBMemRequest": {
"type": "string"
},
"logMemRequest": {
"type": "string"
},
"nodeSelector": {
"type": "array",
"items": {
@@ -4891,6 +4946,49 @@ func init() {
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/pvcs/{PVCName}/events": {
"get": {
"tags": [
"OperatorAPI"
],
"summary": "Get Events for PVC",
"operationId": "GetPVCEvents",
"parameters": [
{
"type": "string",
"name": "namespace",
"in": "path",
"required": true
},
{
"type": "string",
"name": "tenant",
"in": "path",
"required": true
},
{
"type": "string",
"name": "PVCName",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/eventListWrapper"
}
},
"default": {
"description": "Generic error response.",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
},
"/namespaces/{namespace}/tenants/{tenant}/security": {
"get": {
"tags": [
@@ -7533,6 +7631,18 @@ func init() {
"$ref": "#/definitions/label"
}
},
"logCPURequest": {
"type": "string"
},
"logDBCPURequest": {
"type": "string"
},
"logDBMemRequest": {
"type": "string"
},
"logMemRequest": {
"type": "string"
},
"nodeSelector": {
"type": "array",
"items": {

View File

@@ -1,3 +1,19 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package operatorapi
import (

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -99,6 +99,9 @@ func NewOperatorAPI(spec *loads.Document) *OperatorAPI {
OperatorAPIGetMaxAllocatableMemHandler: operator_api.GetMaxAllocatableMemHandlerFunc(func(params operator_api.GetMaxAllocatableMemParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.GetMaxAllocatableMem has not yet been implemented")
}),
OperatorAPIGetPVCEventsHandler: operator_api.GetPVCEventsHandlerFunc(func(params operator_api.GetPVCEventsParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.GetPVCEvents has not yet been implemented")
}),
OperatorAPIGetParityHandler: operator_api.GetParityHandlerFunc(func(params operator_api.GetParityParams, principal *models.Principal) middleware.Responder {
return middleware.NotImplemented("operation operator_api.GetParity has not yet been implemented")
}),
@@ -274,6 +277,8 @@ type OperatorAPI struct {
OperatorAPIGetDirectCSIVolumeListHandler operator_api.GetDirectCSIVolumeListHandler
// OperatorAPIGetMaxAllocatableMemHandler sets the operation handler for the get max allocatable mem operation
OperatorAPIGetMaxAllocatableMemHandler operator_api.GetMaxAllocatableMemHandler
// OperatorAPIGetPVCEventsHandler sets the operation handler for the get p v c events operation
OperatorAPIGetPVCEventsHandler operator_api.GetPVCEventsHandler
// OperatorAPIGetParityHandler sets the operation handler for the get parity operation
OperatorAPIGetParityHandler operator_api.GetParityHandler
// OperatorAPIGetPodEventsHandler sets the operation handler for the get pod events operation
@@ -459,6 +464,9 @@ func (o *OperatorAPI) Validate() error {
if o.OperatorAPIGetMaxAllocatableMemHandler == nil {
unregistered = append(unregistered, "operator_api.GetMaxAllocatableMemHandler")
}
if o.OperatorAPIGetPVCEventsHandler == nil {
unregistered = append(unregistered, "operator_api.GetPVCEventsHandler")
}
if o.OperatorAPIGetParityHandler == nil {
unregistered = append(unregistered, "operator_api.GetParityHandler")
}
@@ -710,6 +718,10 @@ func (o *OperatorAPI) initHandlerCache() {
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/namespaces/{namespace}/tenants/{tenant}/pvcs/{PVCName}/events"] = operator_api.NewGetPVCEvents(o.context, o.OperatorAPIGetPVCEventsHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)
}
o.handlers["GET"]["/get-parity/{nodes}/{disksPerNode}"] = operator_api.NewGetParity(o.context, o.OperatorAPIGetParityHandler)
if o.handlers["GET"] == nil {
o.handlers["GET"] = make(map[string]http.Handler)

View File

@@ -0,0 +1,88 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"net/http"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
)
// GetPVCEventsHandlerFunc turns a function with the right signature into a get p v c events handler
type GetPVCEventsHandlerFunc func(GetPVCEventsParams, *models.Principal) middleware.Responder
// Handle executing the request and returning a response
func (fn GetPVCEventsHandlerFunc) Handle(params GetPVCEventsParams, principal *models.Principal) middleware.Responder {
return fn(params, principal)
}
// GetPVCEventsHandler interface for that can handle valid get p v c events params
type GetPVCEventsHandler interface {
Handle(GetPVCEventsParams, *models.Principal) middleware.Responder
}
// NewGetPVCEvents creates a new http.Handler for the get p v c events operation
func NewGetPVCEvents(ctx *middleware.Context, handler GetPVCEventsHandler) *GetPVCEvents {
return &GetPVCEvents{Context: ctx, Handler: handler}
}
/* GetPVCEvents swagger:route GET /namespaces/{namespace}/tenants/{tenant}/pvcs/{PVCName}/events OperatorAPI getPVCEvents
Get Events for PVC
*/
type GetPVCEvents struct {
Context *middleware.Context
Handler GetPVCEventsHandler
}
func (o *GetPVCEvents) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
route, rCtx, _ := o.Context.RouteInfo(r)
if rCtx != nil {
*r = *rCtx
}
var Params = NewGetPVCEventsParams()
uprinc, aCtx, err := o.Context.Authorize(r, route)
if err != nil {
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
if aCtx != nil {
*r = *aCtx
}
var principal *models.Principal
if uprinc != nil {
principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise
}
if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params
o.Context.Respond(rw, r, route.Produces, route, err)
return
}
res := o.Handler.Handle(Params, principal) // actually handle the request
o.Context.Respond(rw, r, route.Produces, route, res)
}

View File

@@ -0,0 +1,136 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/errors"
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
)
// NewGetPVCEventsParams creates a new GetPVCEventsParams object
//
// There are no default values defined in the spec.
func NewGetPVCEventsParams() GetPVCEventsParams {
return GetPVCEventsParams{}
}
// GetPVCEventsParams contains all the bound params for the get p v c events operation
// typically these are obtained from a http.Request
//
// swagger:parameters GetPVCEvents
type GetPVCEventsParams struct {
// HTTP Request Object
HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: path
*/
PVCName string
/*
Required: true
In: path
*/
Namespace string
/*
Required: true
In: path
*/
Tenant string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
// for simple values it will use straight method calls.
//
// To ensure default values, the struct must have been initialized with NewGetPVCEventsParams() beforehand.
func (o *GetPVCEventsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error {
var res []error
o.HTTPRequest = r
rPVCName, rhkPVCName, _ := route.Params.GetOK("PVCName")
if err := o.bindPVCName(rPVCName, rhkPVCName, route.Formats); err != nil {
res = append(res, err)
}
rNamespace, rhkNamespace, _ := route.Params.GetOK("namespace")
if err := o.bindNamespace(rNamespace, rhkNamespace, route.Formats); err != nil {
res = append(res, err)
}
rTenant, rhkTenant, _ := route.Params.GetOK("tenant")
if err := o.bindTenant(rTenant, rhkTenant, route.Formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
// bindPVCName binds and validates parameter PVCName from path.
func (o *GetPVCEventsParams) bindPVCName(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.PVCName = raw
return nil
}
// bindNamespace binds and validates parameter Namespace from path.
func (o *GetPVCEventsParams) bindNamespace(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Namespace = raw
return nil
}
// bindTenant binds and validates parameter Tenant from path.
func (o *GetPVCEventsParams) bindTenant(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string
if len(rawData) > 0 {
raw = rawData[len(rawData)-1]
}
// Required: true
// Parameter is provided by construction from the route
o.Tenant = raw
return nil
}

View File

@@ -0,0 +1,136 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command
import (
"net/http"
"github.com/go-openapi/runtime"
"github.com/minio/console/models"
)
// GetPVCEventsOKCode is the HTTP code returned for type GetPVCEventsOK
const GetPVCEventsOKCode int = 200
/*GetPVCEventsOK A successful response.
swagger:response getPVCEventsOK
*/
type GetPVCEventsOK struct {
/*
In: Body
*/
Payload models.EventListWrapper `json:"body,omitempty"`
}
// NewGetPVCEventsOK creates GetPVCEventsOK with default headers values
func NewGetPVCEventsOK() *GetPVCEventsOK {
return &GetPVCEventsOK{}
}
// WithPayload adds the payload to the get p v c events o k response
func (o *GetPVCEventsOK) WithPayload(payload models.EventListWrapper) *GetPVCEventsOK {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get p v c events o k response
func (o *GetPVCEventsOK) SetPayload(payload models.EventListWrapper) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetPVCEventsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(200)
payload := o.Payload
if payload == nil {
// return empty array
payload = models.EventListWrapper{}
}
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
/*GetPVCEventsDefault Generic error response.
swagger:response getPVCEventsDefault
*/
type GetPVCEventsDefault struct {
_statusCode int
/*
In: Body
*/
Payload *models.Error `json:"body,omitempty"`
}
// NewGetPVCEventsDefault creates GetPVCEventsDefault with default headers values
func NewGetPVCEventsDefault(code int) *GetPVCEventsDefault {
if code <= 0 {
code = 500
}
return &GetPVCEventsDefault{
_statusCode: code,
}
}
// WithStatusCode adds the status to the get p v c events default response
func (o *GetPVCEventsDefault) WithStatusCode(code int) *GetPVCEventsDefault {
o._statusCode = code
return o
}
// SetStatusCode sets the status to the get p v c events default response
func (o *GetPVCEventsDefault) SetStatusCode(code int) {
o._statusCode = code
}
// WithPayload adds the payload to the get p v c events default response
func (o *GetPVCEventsDefault) WithPayload(payload *models.Error) *GetPVCEventsDefault {
o.Payload = payload
return o
}
// SetPayload sets the payload to the get p v c events default response
func (o *GetPVCEventsDefault) SetPayload(payload *models.Error) {
o.Payload = payload
}
// WriteResponse to the client
func (o *GetPVCEventsDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) {
rw.WriteHeader(o._statusCode)
if o.Payload != nil {
payload := o.Payload
if err := producer.Produce(rw, payload); err != nil {
panic(err) // let the recovery middleware deal with this
}
}
}

View File

@@ -0,0 +1,132 @@
// Code generated by go-swagger; DO NOT EDIT.
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operator_api
// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the generate command
import (
"errors"
"net/url"
golangswaggerpaths "path"
"strings"
)
// GetPVCEventsURL generates an URL for the get p v c events operation
type GetPVCEventsURL struct {
PVCName string
Namespace string
Tenant string
_basePath string
// avoid unkeyed usage
_ struct{}
}
// WithBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetPVCEventsURL) WithBasePath(bp string) *GetPVCEventsURL {
o.SetBasePath(bp)
return o
}
// SetBasePath sets the base path for this url builder, only required when it's different from the
// base path specified in the swagger spec.
// When the value of the base path is an empty string
func (o *GetPVCEventsURL) SetBasePath(bp string) {
o._basePath = bp
}
// Build a url path and query string
func (o *GetPVCEventsURL) Build() (*url.URL, error) {
var _result url.URL
var _path = "/namespaces/{namespace}/tenants/{tenant}/pvcs/{PVCName}/events"
pVCName := o.PVCName
if pVCName != "" {
_path = strings.Replace(_path, "{PVCName}", pVCName, -1)
} else {
return nil, errors.New("pVCName is required on GetPVCEventsURL")
}
namespace := o.Namespace
if namespace != "" {
_path = strings.Replace(_path, "{namespace}", namespace, -1)
} else {
return nil, errors.New("namespace is required on GetPVCEventsURL")
}
tenant := o.Tenant
if tenant != "" {
_path = strings.Replace(_path, "{tenant}", tenant, -1)
} else {
return nil, errors.New("tenant is required on GetPVCEventsURL")
}
_basePath := o._basePath
if _basePath == "" {
_basePath = "/api/v1"
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
return &_result, nil
}
// Must is a helper function to panic when the url builder returns an error
func (o *GetPVCEventsURL) Must(u *url.URL, err error) *url.URL {
if err != nil {
panic(err)
}
if u == nil {
panic("url can't be nil")
}
return u
}
// String returns the string representation of the path with query string
func (o *GetPVCEventsURL) String() string {
return o.Must(o.Build()).String()
}
// BuildFull builds a full url with scheme, host, path and query string
func (o *GetPVCEventsURL) BuildFull(scheme, host string) (*url.URL, error) {
if scheme == "" {
return nil, errors.New("scheme is required for a full url on GetPVCEventsURL")
}
if host == "" {
return nil, errors.New("host is required for a full url on GetPVCEventsURL")
}
base, err := o.Build()
if err != nil {
return nil, err
}
base.Scheme = scheme
base.Host = host
return base, nil
}
// StringFull returns the string representation of a complete url
func (o *GetPVCEventsURL) StringFull(scheme, host string) string {
return o.Must(o.BuildFull(scheme, host)).String()
}

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,416 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
package operatorapi
import (
"context"
"errors"
"time"
"github.com/minio/console/pkg/subnet"
miniov2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/minio/console/restapi"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/cluster"
"github.com/minio/console/models"
"github.com/minio/console/operatorapi/operations"
"github.com/minio/console/operatorapi/operations/operator_api"
)
func registerSubscriptionHandlers(api *operations.OperatorAPI) {
// Activate license subscription for a particular tenant
api.OperatorAPISubscriptionActivateHandler = operator_api.SubscriptionActivateHandlerFunc(func(params operator_api.SubscriptionActivateParams, session *models.Principal) middleware.Responder {
err := getSubscriptionActivateResponse(session, params.Namespace, params.Tenant)
if err != nil {
return operator_api.NewSubscriptionActivateDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionActivateNoContent()
})
// Refresh license for k8s cluster
api.OperatorAPISubscriptionRefreshHandler = operator_api.SubscriptionRefreshHandlerFunc(func(params operator_api.SubscriptionRefreshParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionRefreshResponse(session)
if err != nil {
return operator_api.NewSubscriptionRefreshDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionRefreshOK().WithPayload(license)
})
// Validate subscription handler
api.OperatorAPISubscriptionValidateHandler = operator_api.SubscriptionValidateHandlerFunc(func(params operator_api.SubscriptionValidateParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionValidateResponse(session, params.Body)
if err != nil {
return operator_api.NewSubscriptionValidateDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionValidateOK().WithPayload(license)
})
// Get subscription information handler
api.OperatorAPISubscriptionInfoHandler = operator_api.SubscriptionInfoHandlerFunc(func(params operator_api.SubscriptionInfoParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionInfoResponse(session)
if err != nil {
return operator_api.NewSubscriptionInfoDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionInfoOK().WithPayload(license)
})
// Refresh license for k8s cluster
api.OperatorAPISubscriptionRefreshHandler = operator_api.SubscriptionRefreshHandlerFunc(func(params operator_api.SubscriptionRefreshParams, session *models.Principal) middleware.Responder {
license, err := getSubscriptionRefreshResponse(session)
if err != nil {
return operator_api.NewSubscriptionRefreshDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewSubscriptionRefreshOK().WithPayload(license)
})
}
// retrieveLicense returns license from K8S secrets
func retrieveLicense(ctx context.Context, sessionToken string) (string, error) {
var license string
// configure kubernetes client
clientSet, err := cluster.K8sClient(sessionToken)
if err != nil {
return "", err
}
k8sClient := k8sClient{
client: clientSet,
}
// Get cluster subscription license
license, err = getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if err != nil {
return "", err
}
return license, nil
}
// getSubscriptionLicense will retrieve stored license jwt from k8s secret
func getSubscriptionLicense(ctx context.Context, clientSet K8sClientI, namespace, secretName string) (string, error) {
// retrieve license stored in k8s
licenseSecret, err := clientSet.getSecret(ctx, namespace, secretName, metav1.GetOptions{})
if err != nil {
return "", err
}
license, ok := licenseSecret.Data[ConsoleSubnetLicense]
if !ok {
LogError("subnet secret does not contain a valid subnet license")
return "", restapi.ErrorGeneric
}
return string(license), nil
}
// addSubscriptionLicenseToTenant replace existing console tenant secret and adds the subnet license key
func addSubscriptionLicenseToTenant(ctx context.Context, clientSet K8sClientI, opClient OperatorClientI, license string, tenant *miniov2.Tenant) error {
// If Tenant has a configuration secret update the license there and MinIO pods doesn't need to get restarted
if tenant.HasConfigurationSecret() {
// Update the Tenant Configuration
tenantConfigurationSecret, err := clientSet.getSecret(ctx, tenant.Namespace, tenant.Spec.Configuration.Name, metav1.GetOptions{})
if err != nil {
return err
}
if _, ok := tenantConfigurationSecret.Data["config.env"]; ok {
updatedTenantConfiguration := map[string]string{}
tenantConfigurationMap := miniov2.ParseRawConfiguration(tenantConfigurationSecret.Data["config.env"])
for key, val := range tenantConfigurationMap {
updatedTenantConfiguration[key] = string(val)
}
updatedTenantConfiguration[MinIOSubnetLicense] = license
// removing accesskey & secretkey that are added automatically by parsing function
// and are not need it for the tenant itself
delete(updatedTenantConfiguration, "accesskey")
delete(updatedTenantConfiguration, "secretkey")
tenantConfigurationSecret.Data = map[string][]byte{
"config.env": []byte(GenerateTenantConfigurationFile(updatedTenantConfiguration)),
}
_, err = clientSet.updateSecret(ctx, tenant.Namespace, tenantConfigurationSecret, metav1.UpdateOptions{})
if err != nil {
return err
}
} else {
return errors.New("tenant configuration secret has wrong format")
}
} else {
// If configuration file is not present set the license to the container env
updatedTenant := tenant.DeepCopy()
// reset container env vars
updatedTenant.Spec.Env = []corev1.EnvVar{}
var licenseIsSet bool
for _, env := range tenant.GetEnvVars() {
// check if license already exists and override
if env.Name == MinIOSubnetLicense {
updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, corev1.EnvVar{
Name: MinIOSubnetLicense,
Value: license,
})
licenseIsSet = true
} else {
// copy existing container env variables
updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, env)
}
}
// if license didnt exists append it
if !licenseIsSet {
updatedTenant.Spec.Env = append(updatedTenant.Spec.Env, corev1.EnvVar{
Name: MinIOSubnetLicense,
Value: license,
})
}
// this will start MinIO pods rolling restart
_, err := opClient.TenantUpdate(ctx, updatedTenant, metav1.UpdateOptions{})
if err != nil {
return err
}
}
return nil
}
func getSubscriptionRefreshResponse(session *models.Principal) (*models.License, *models.Error) {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseKey, err := retrieveLicense(ctx, session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
newLicenseInfo, licenseRaw, err := subscriptionRefresh(client, licenseKey)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
// configure kubernetes client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
k8sClient := k8sClient{
client: clientSet,
}
// save license key to k8s and restart all console pods
if err = saveSubscriptionLicense(ctx, &k8sClient, licenseRaw); err != nil {
return nil, prepareError(restapi.ErrorGeneric, nil, err)
}
// update license for all existing tenants
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
opClient := operatorClient{
client: opClientClientSet,
}
// iterate over all tenants and update licenses
tenants, err := opClient.TenantList(ctx, "", metav1.ListOptions{})
if err != nil {
return nil, prepareError(err)
}
for _, tenant := range tenants.Items {
if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, &opClient, licenseRaw, &tenant); err != nil {
return nil, prepareError(err)
}
}
return newLicenseInfo, nil
}
// RefreshLicense will check current subnet license and try to renew it
func RefreshLicense() error {
// Get current license
saK8SToken := getK8sSAToken()
licenseKey, err := retrieveLicense(context.Background(), saK8SToken)
if licenseKey == "" {
return errors.New("no license present")
}
if err != nil {
return err
}
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
// Attempt to refresh license
_, refreshedLicenseKey, err := subscriptionRefresh(client, licenseKey)
if err != nil {
return err
}
if refreshedLicenseKey == "" {
return errors.New("license expired, please open a support ticket at https://subnet.min.io/")
}
// store new license in memory for console ui
LicenseKey = refreshedLicenseKey
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
clientSet, err := cluster.K8sClient(saK8SToken)
if err != nil {
return err
}
k8sClient := k8sClient{
client: clientSet,
}
return saveSubscriptionLicense(ctx, &k8sClient, refreshedLicenseKey)
}
func subscriptionRefresh(httpClient *cluster.HTTPClient, license string) (*models.License, string, error) {
licenseInfo, rawLicense, err := subnet.RefreshLicense(httpClient, license)
if err != nil {
return nil, "", err
}
return &models.License{
Email: licenseInfo.Email,
AccountID: licenseInfo.AccountID,
StorageCapacity: licenseInfo.StorageCapacity,
Plan: licenseInfo.Plan,
ExpiresAt: licenseInfo.ExpiresAt.String(),
Organization: licenseInfo.Organization,
}, rawLicense, nil
}
// saveSubscriptionLicense will create or replace an existing subnet license secret in the k8s cluster
func saveSubscriptionLicense(ctx context.Context, clientSet K8sClientI, license string) error {
licenseSecret, err := clientSet.getSecret(ctx, cluster.Namespace, OperatorSubnetLicenseSecretName, metav1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
// Save subnet license in k8s secrets
licenseSecret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Data: map[string][]byte{
ConsoleSubnetLicense: []byte(license),
},
}
_, err = clientSet.createSecret(ctx, cluster.Namespace, licenseSecret, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}
return err
}
// update existing license
licenseSecret.Data = map[string][]byte{
ConsoleSubnetLicense: []byte(license),
}
_, err = clientSet.updateSecret(ctx, cluster.Namespace, licenseSecret, metav1.UpdateOptions{})
if err != nil {
return err
}
return nil
}
// subscriptionValidate will validate the provided jwt license against the subnet public key
func subscriptionValidate(client cluster.HTTPClientI, license, email, password string) (*models.License, string, error) {
licenseInfo, rawLicense, err := subnet.ValidateLicense(client, license, email, password)
if err != nil {
return nil, "", err
}
return &models.License{
Email: licenseInfo.Email,
AccountID: licenseInfo.AccountID,
StorageCapacity: licenseInfo.StorageCapacity,
Plan: licenseInfo.Plan,
ExpiresAt: licenseInfo.ExpiresAt.String(),
Organization: licenseInfo.Organization,
}, rawLicense, nil
}
// getSubscriptionValidateResponse
func getSubscriptionValidateResponse(session *models.Principal, params *models.SubscriptionValidateRequest) (*models.License, *models.Error) {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
// validate license key
licenseInfo, license, err := subscriptionValidate(client, params.License, params.Email, params.Password)
if err != nil {
return nil, prepareError(errInvalidLicense, nil, err)
}
// configure kubernetes client
clientSet, err := cluster.K8sClient(session.STSSessionToken)
k8sClient := k8sClient{
client: clientSet,
}
if err != nil {
return nil, prepareError(errorGeneric, nil, err)
}
// save license key to k8s
if err = saveSubscriptionLicense(ctx, &k8sClient, license); err != nil {
return nil, prepareError(errorGeneric, nil, err)
}
return licenseInfo, nil
}
func getSubscriptionActivateResponse(session *models.Principal, namespace, tenantName string) *models.Error {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
opClientClientSet, err := cluster.OperatorClient(session.STSSessionToken)
if err != nil {
return prepareError(errorGeneric, nil, err)
}
clientSet, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return prepareError(errorGeneric, nil, err)
}
opClient := operatorClient{
client: opClientClientSet,
}
tenant, err := getTenant(ctx, &opClient, namespace, tenantName)
if err != nil {
return prepareError(err, errorGeneric)
}
// configure kubernetes client
k8sClient := k8sClient{
client: clientSet,
}
// Get cluster subscription license
license, err := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if err != nil {
return prepareError(errInvalidCredentials, nil, err)
}
// add subscription license to existing console Tenant
if err = addSubscriptionLicenseToTenant(ctx, &k8sClient, &opClient, license, tenant); err != nil {
return prepareError(err, errorGeneric)
}
return nil
}
// getSubscriptionInfoResponse returns information about the current configured subnet license for Console
func getSubscriptionInfoResponse(session *models.Principal) (*models.License, *models.Error) {
// 20 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
var licenseInfo *models.License
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseKey, err := retrieveLicense(ctx, session.STSSessionToken)
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
// validate license key and obtain license info
licenseInfo, _, err = subscriptionValidate(client, licenseKey, "", "")
if err != nil {
return nil, prepareError(errLicenseNotFound, nil, err)
}
return licenseInfo, nil
}

View File

@@ -1,359 +0,0 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package operatorapi
import (
"context"
"testing"
v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
"errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func Test_addSubscriptionLicenseToTenant(t *testing.T) {
k8sClient := k8sClientMock{}
opClient := opClientMock{}
tenant := &v2.Tenant{
ObjectMeta: metav1.ObjectMeta{},
Spec: v2.TenantSpec{},
}
type args struct {
ctx context.Context
clientSet K8sClientI
opClient OperatorClientI
license string
tenant *v2.Tenant
}
tests := []struct {
name string
args args
wantErr bool
mockFunc func()
}{
{
name: "success updating subscription for tenant with configuration file",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: false,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "minio-configuration",
},
Data: map[string][]byte{
"config.env": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""),
},
}, nil
}
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, nil
}
},
},
{
name: "error updating subscription for tenant because cannot get configuration file",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return nil, errors.New("something wrong happened")
}
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, nil
}
},
},
{
name: "error updating subscription for tenant because configuration file has wrong format",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "minio-configuration",
},
Data: map[string][]byte{
"aaaaa": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""),
},
}, nil
}
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, nil
}
},
},
{
name: "error updating subscription for tenant because cannot update configuration file",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Configuration = &corev1.LocalObjectReference{
Name: "minio-configuration",
}
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "minio-configuration",
},
Data: map[string][]byte{
"config.env": []byte("export MINIO_SUBNET_LICENSE=\"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I\""),
},
}, nil
}
UpdateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) {
return nil, errors.New("something wrong happened")
}
},
},
{
name: "success updating subscription for tenant with env variable",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: false,
mockFunc: func() {
tenant.Spec.Env = []corev1.EnvVar{
{
Name: "MINIO_SUBNET_LICENSE",
Value: "",
ValueFrom: nil,
},
}
opClientTenantUpdateMock = func(ctx context.Context, tenant *v2.Tenant, opts metav1.UpdateOptions) (*v2.Tenant, error) {
return nil, nil
}
},
},
{
name: "error updating subscription for tenant with env variable because of update tenant error",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
opClient: opClient,
license: "",
tenant: tenant,
},
wantErr: true,
mockFunc: func() {
tenant.Spec.Env = []corev1.EnvVar{
{
Name: "MINIO_SUBNET_LICENSE",
Value: "",
ValueFrom: nil,
},
}
opClientTenantUpdateMock = func(ctx context.Context, tenant *v2.Tenant, opts metav1.UpdateOptions) (*v2.Tenant, error) {
return nil, errors.New("something wrong happened")
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
if err := addSubscriptionLicenseToTenant(tt.args.ctx, tt.args.clientSet, tt.args.opClient, tt.args.license, tt.args.tenant); (err != nil) != tt.wantErr {
t.Errorf("addSubscriptionLicenseToTenant() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_saveSubscriptionLicense(t *testing.T) {
k8sClient := k8sClientMock{}
type args struct {
ctx context.Context
clientSet K8sClientI
license string
}
tests := []struct {
name string
args args
wantErr bool
mockFunc func()
}{
{
name: "error deleting existing secret",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
license: "1111111111",
},
mockFunc: func() {
DeleteSecretMock = func(ctx context.Context, namespace string, name string, opts metav1.DeleteOptions) error {
return nil
}
CreateSecretMock = func(ctx context.Context, namespace string, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) {
return nil, errors.New("something went wrong")
}
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
if err := saveSubscriptionLicense(tt.args.ctx, tt.args.clientSet, tt.args.license); (err != nil) != tt.wantErr {
t.Errorf("saveSubscriptionLicense() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func Test_getSubscriptionLicense(t *testing.T) {
license := "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJsZW5pbitjMUBtaW5pby5pbyIsInRlYW1OYW1lIjoiY29uc29sZS1jdXN0b21lciIsImV4cCI6MS42Mzk5NTI2MTE2MDkxNDQ3MzJlOSwiaXNzIjoic3VibmV0QG1pbmlvLmlvIiwiY2FwYWNpdHkiOjI1LCJpYXQiOjEuNjA4NDE2NjExNjA5MTQ0NzMyZTksImFjY291bnRJZCI6MTc2LCJzZXJ2aWNlVHlwZSI6IlNUQU5EQVJEIn0.ndtf8V_FJTvhXeemVLlORyDev6RJaSPhZ2djkMVK9SvXD0srR_qlYJATPjC4NljkS71nXMGVDov5uCTuUL97x6FGQEKDruA-z24x_2Zr8kof4LfBb3HUHudCR8QvE--I"
k8sClient := k8sClientMock{}
type args struct {
ctx context.Context
clientSet K8sClientI
namespace string
secretName string
}
tests := []struct {
name string
args args
want string
wantErr bool
mockFunc func()
}{
{
name: "error because subscription license doesnt exists",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
namespace: "namespace",
secretName: OperatorSubnetLicenseSecretName,
},
wantErr: true,
mockFunc: func() {
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error because license field doesnt exist in k8s secret",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
namespace: "namespace",
secretName: OperatorSubnetLicenseSecretName,
},
wantErr: true,
mockFunc: func() {
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
imm := true
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Immutable: &imm,
Data: map[string][]byte{
//ConsoleSubnetLicense: []byte(license),
},
}, nil
}
},
},
{
name: "license obtained successfully",
args: args{
ctx: context.Background(),
clientSet: k8sClient,
namespace: "namespace",
secretName: OperatorSubnetLicenseSecretName,
},
wantErr: false,
want: license,
mockFunc: func() {
k8sclientGetSecretMock = func(ctx context.Context, namespace, secretName string, opts metav1.GetOptions) (*corev1.Secret, error) {
imm := true
return &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: OperatorSubnetLicenseSecretName,
},
Immutable: &imm,
Data: map[string][]byte{
ConsoleSubnetLicense: []byte(license),
},
}, nil
}
},
},
}
for _, tt := range tests {
if tt.mockFunc != nil {
tt.mockFunc()
}
t.Run(tt.name, func(t *testing.T) {
got, err := getSubscriptionLicense(tt.args.ctx, tt.args.clientSet, tt.args.namespace, tt.args.secretName)
if (err != nil) != tt.wantErr {
t.Errorf("getSubscriptionLicense() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getSubscriptionLicense() got = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
@@ -28,6 +28,7 @@ import (
"net"
"net/http"
"os"
"reflect"
"sort"
"strconv"
"strings"
@@ -237,6 +238,7 @@ func registerTenantHandlers(api *operations.OperatorAPI) {
//Get tenant monitoring info
api.OperatorAPIGetTenantMonitoringHandler = operator_api.GetTenantMonitoringHandlerFunc(func(params operator_api.GetTenantMonitoringParams, session *models.Principal) middleware.Responder {
payload, err := getTenantMonitoringResponse(session, params)
if err != nil {
return operator_api.NewGetTenantMonitoringDefault(int(err.Code)).WithPayload(err)
@@ -558,18 +560,6 @@ func getTenantDetailsResponse(session *models.Principal, params operator_api.Ten
info.IdpOidcEnabled = oidcEnabled
info.MinioTLS = minTenant.TLS()
// obtain current subnet license for tenant (if exists)
if license, ok := tenantConfiguration[MinIOSubnetLicense]; ok {
client := &cluster.HTTPClient{
Client: restapi.GetConsoleHTTPClient(),
}
licenseInfo, _, _ := subscriptionValidate(client, string(license), "", "")
// if licenseInfo is present attach it to the tenantInfo response
if licenseInfo != nil {
info.SubnetLicense = licenseInfo
}
}
// attach status information
info.Status = &models.TenantStatus{
HealthStatus: string(minTenant.Status.HealthStatus),
@@ -1241,13 +1231,6 @@ func getTenantCreatedResponse(session *models.Principal, params operator_api.Cre
}
}
// If Subnet License is present in k8s secrets, copy that to the MINIO_SUBNET_LICENSE env variable
// of the console tenant
license, _ := getSubscriptionLicense(ctx, &k8sClient, cluster.Namespace, OperatorSubnetLicenseSecretName)
if license != "" {
tenantConfigurationENV[MinIOSubnetLicense] = license
}
// add annotations
var annotations map[string]string
@@ -1808,14 +1791,6 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen
minTenant.Spec.Log.Audit = &miniov2.AuditConfig{DiskCapacityGB: swag.Int(0)}
}
/*if minTenant.Spec.Log.Image == "" {
minTenant.Spec.Log.Image = miniov2.DefaultLogSearchAPIImage
}
if minTenant.Spec.Log.Db.Image == "" {
minTenant.Spec.Log.Db.Image = miniov2.LogPgImage
}*/
retval := &models.TenantLogs{
Image: minTenant.Spec.Log.Image,
DiskCapacityGB: fmt.Sprintf("%d", *minTenant.Spec.Log.Audit.DiskCapacityGB),
@@ -1830,11 +1805,33 @@ func getTenantLogsResponse(session *models.Principal, params operator_api.GetTen
DbServiceAccountName: minTenant.Spec.Log.Db.ServiceAccountName,
Disabled: false,
}
var requestedCPU string
var requestedMem string
var requestedDBCPU string
var requestedDBMem string
if minTenant.Spec.Log.Resources.Requests != nil {
requestedCPUQ := minTenant.Spec.Log.Resources.Requests["cpu"]
requestedCPU = strconv.FormatInt(requestedCPUQ.Value(), 10)
requestedMemQ := minTenant.Spec.Log.Resources.Requests["memory"]
requestedMem = strconv.FormatInt(requestedMemQ.Value(), 10)
requestedDBCPUQ := minTenant.Spec.Log.Db.Resources.Requests["cpu"]
requestedDBCPU = strconv.FormatInt(requestedDBCPUQ.Value(), 10)
requestedDBMemQ := minTenant.Spec.Log.Db.Resources.Requests["memory"]
requestedDBMem = strconv.FormatInt(requestedDBMemQ.Value(), 10)
retval.LogCPURequest = requestedCPU
retval.LogMemRequest = requestedMem
retval.LogDBCPURequest = requestedDBCPU
retval.LogDBMemRequest = requestedDBMem
}
return retval, nil
}
// setTenantLogsResponse returns the logs of a tenant
func setTenantLogsResponse(session *models.Principal, params operator_api.SetTenantLogsParams) (bool, *models.Error) {
// 30 seconds timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -1852,6 +1849,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
if err != nil {
return false, prepareError(err, errorUnableToGetTenantUsage)
}
var labels = make(map[string]string)
for i := 0; i < len(params.Data.Labels); i++ {
if params.Data.Labels[i] != nil {
@@ -1873,6 +1871,26 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
}
}
minTenant.Spec.Log.NodeSelector = nodeSelector
logResourceRequest := make(corev1.ResourceList)
if reflect.TypeOf(params.Data.LogCPURequest).Kind() == reflect.String && params.Data.LogCPURequest != "0Gi" && params.Data.LogCPURequest != "" {
cpuQuantity, err := resource.ParseQuantity(params.Data.LogCPURequest)
if err != nil {
return false, prepareError(err)
}
logResourceRequest["cpu"] = cpuQuantity
minTenant.Spec.Log.Resources.Requests = logResourceRequest
}
if reflect.TypeOf(params.Data.LogMemRequest).Kind() == reflect.String {
memQuantity, err := resource.ParseQuantity(params.Data.LogMemRequest)
if err != nil {
return false, prepareError(err)
}
logResourceRequest["memory"] = memQuantity
minTenant.Spec.Log.Resources.Requests = logResourceRequest
}
modified := false
if minTenant.Spec.Log.Db != nil {
modified = true
@@ -1898,6 +1916,24 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
}
modified = true
}
logDBResourceRequest := make(corev1.ResourceList)
if reflect.TypeOf(params.Data.LogDBCPURequest).Kind() == reflect.String && params.Data.LogDBCPURequest != "0Gi" && params.Data.LogDBCPURequest != "" {
dbCPUQuantity, err := resource.ParseQuantity(params.Data.LogDBCPURequest)
if err != nil {
return false, prepareError(err)
}
logDBResourceRequest["cpu"] = dbCPUQuantity
minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest
}
if reflect.TypeOf(params.Data.LogDBMemRequest).Kind() == reflect.String {
dbMemQuantity, err := resource.ParseQuantity(params.Data.LogDBMemRequest)
if err != nil {
return false, prepareError(err)
}
logDBResourceRequest["memory"] = dbMemQuantity
minTenant.Spec.Log.Db.Resources.Requests = logDBResourceRequest
}
minTenant.Spec.Log.Image = params.Data.Image
diskCapacityGB, err := strconv.Atoi(params.Data.DiskCapacityGB)
if err == nil {
@@ -1941,6 +1977,9 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
NodeSelector: dbNodeSelector,
Image: params.Data.DbImage,
ServiceAccountName: params.Data.DbServiceAccountName,
Resources: corev1.ResourceRequirements{
Requests: minTenant.Spec.Log.Db.Resources.Requests,
},
}
} else {
minTenant.Spec.Log.Db.Labels = dbLabels
@@ -1950,6 +1989,7 @@ func setTenantLogsResponse(session *models.Principal, params operator_api.SetTen
minTenant.Spec.Log.Db.ServiceAccountName = params.Data.DbServiceAccountName
}
}
_, err = opClient.TenantUpdate(ctx, minTenant, metav1.UpdateOptions{})
if err != nil {
return false, prepareError(err)
@@ -2069,9 +2109,13 @@ func getTenantPodsResponse(session *models.Principal, params operator_api.GetTen
if len(pod.Status.ContainerStatuses) > 0 {
restarts = int64(pod.Status.ContainerStatuses[0].RestartCount)
}
status := string(pod.Status.Phase)
if pod.DeletionTimestamp != nil {
status = "Terminating"
}
retval = append(retval, &models.TenantPod{
Name: swag.String(pod.Name),
Status: string(pod.Status.Phase),
Status: status,
TimeCreated: pod.CreationTimestamp.Unix(),
PodIP: pod.Status.PodIP,
Restarts: restarts,

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
@@ -19,6 +19,7 @@ package operatorapi
import (
"context"
"fmt"
"sort"
miniov1 "github.com/minio/operator/pkg/apis/minio.min.io/v1"
@@ -58,6 +59,17 @@ func registerVolumesHandlers(api *operations.OperatorAPI) {
}
return nil
})
api.OperatorAPIGetPVCEventsHandler = operator_api.GetPVCEventsHandlerFunc(func(params operator_api.GetPVCEventsParams, session *models.Principal) middleware.Responder {
payload, err := getPVCEventsResponse(session, params)
if err != nil {
return operator_api.NewGetPVCEventsDefault(int(err.Code)).WithPayload(err)
}
return operator_api.NewGetPVCEventsOK().WithPayload(payload)
})
}
func getPVCsResponse(session *models.Principal) (*models.ListPVCsResponse, *models.Error) {
@@ -83,12 +95,16 @@ func getPVCsResponse(session *models.Principal) (*models.ListPVCsResponse, *mode
var ListPVCs []*models.PvcsListResponse
for _, pvc := range listAllPvcs.Items {
status := string(pvc.Status.Phase)
if pvc.DeletionTimestamp != nil {
status = "Terminating"
}
pvcResponse := models.PvcsListResponse{
Name: pvc.Name,
Age: pvc.CreationTimestamp.String(),
Capacity: pvc.Status.Capacity.Storage().String(),
Namespace: pvc.Namespace,
Status: string(pvc.Status.Phase),
Status: status,
StorageClass: *pvc.Spec.StorageClassName,
Volume: pvc.Spec.VolumeName,
Tenant: pvc.Labels["v1.min.io/tenant"],
@@ -126,12 +142,16 @@ func getPVCsForTenantResponse(session *models.Principal, params operator_api.Lis
var ListPVCs []*models.PvcsListResponse
for _, pvc := range listAllPvcs.Items {
status := string(pvc.Status.Phase)
if pvc.DeletionTimestamp != nil {
status = "Terminating"
}
pvcResponse := models.PvcsListResponse{
Name: pvc.Name,
Age: pvc.CreationTimestamp.String(),
Capacity: pvc.Status.Capacity.Storage().String(),
Namespace: pvc.Namespace,
Status: string(pvc.Status.Phase),
Status: status,
StorageClass: *pvc.Spec.StorageClassName,
Volume: pvc.Spec.VolumeName,
Tenant: pvc.Labels["v1.min.io/tenant"],
@@ -162,3 +182,33 @@ func getDeletePVCResponse(session *models.Principal, params operator_api.DeleteP
}
return nil
}
func getPVCEventsResponse(session *models.Principal, params operator_api.GetPVCEventsParams) (models.EventListWrapper, *models.Error) {
ctx := context.Background()
clientset, err := cluster.K8sClient(session.STSSessionToken)
if err != nil {
return nil, prepareError(err)
}
PVC, err := clientset.CoreV1().PersistentVolumeClaims(params.Namespace).Get(ctx, params.PVCName, metav1.GetOptions{})
if err != nil {
return nil, prepareError(err)
}
events, err := clientset.CoreV1().Events(params.Namespace).List(ctx, metav1.ListOptions{FieldSelector: fmt.Sprintf("involvedObject.uid=%s", PVC.UID)})
if err != nil {
return nil, prepareError(err)
}
retval := models.EventListWrapper{}
for i := 0; i < len(events.Items); i++ {
retval = append(retval, &models.EventListElement{
Namespace: events.Items[i].Namespace,
LastSeen: events.Items[i].LastTimestamp.Unix(),
Message: events.Items[i].Message,
EventType: events.Items[i].Type,
Reason: events.Items[i].Reason,
})
}
sort.SliceStable(retval, func(i int, j int) bool {
return retval[i].LastSeen < retval[j].LastSeen
})
return retval, nil
}

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,3 +1,19 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package operatorapi
import (

View File

@@ -1,3 +1,19 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package kes
import (

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
@@ -20,15 +20,9 @@ import (
"errors"
"log"
"github.com/minio/pkg/env"
"github.com/minio/pkg/licverifier"
)
// GetSubnetURL
func GetSubnetURL() string {
return env.Get(ConsoleSubnetURL, "https://subnet.min.io")
}
// GetLicenseInfoFromJWT will return license metadata from a jwt string license
func GetLicenseInfoFromJWT(license string, publicKeys []string) (*licverifier.LicenseInfo, error) {
if license == "" {
@@ -49,3 +43,15 @@ func GetLicenseInfoFromJWT(license string, publicKeys []string) (*licverifier.Li
}
return nil, errors.New("invalid license key")
}
// MfaReq - JSON payload of the SUBNET mfa api
type MfaReq struct {
Username string `json:"username"`
OTP string `json:"otp"`
Token string `json:"token"`
}
type LoginResp struct {
AccessToken string
MfaToken string
}

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify

View File

@@ -1,4 +1,4 @@
// This file is part of MinIO Kubernetes Cloud
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
@@ -29,9 +29,4 @@ JkO2PfyyAYEO/5dBlPh1Undu9WQl6J7B
const (
// Constants for subnet configuration
ConsoleSubnetURL = "CONSOLE_SUBNET_URL"
// Subnet endpoints
publicKey = "/downloads/license-pubkey.pem"
loginEndpoint = "/api/auth/login"
refreshLicenseKeyEndpoint = "/api/auth/subscription/renew-license"
licenseKeyEndpoint = "/api/auth/subscription/license-key"
)

View File

@@ -22,150 +22,115 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"github.com/minio/pkg/licverifier"
"github.com/minio/console/models"
"github.com/minio/madmin-go"
mc "github.com/minio/mc/cmd"
"github.com/tidwall/gjson"
"github.com/minio/console/cluster"
"github.com/minio/pkg/licverifier"
)
// subnetLoginRequest body request for subnet login
type subnetLoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
func LoginWithMFA(client cluster.HTTPClientI, username, mfaToken, otp string) (*LoginResp, error) {
mfaLoginReq := MfaReq{Username: username, OTP: otp, Token: mfaToken}
resp, err := subnetPostReq(client, subnetMFAURL(), mfaLoginReq, nil)
if err != nil {
return nil, err
}
token := gjson.Get(resp, "token_info.access_token")
if token.Exists() {
return &LoginResp{AccessToken: token.String(), MfaToken: ""}, nil
}
return nil, errors.New("access token not found in response")
}
// tokenInfo
type tokenInfo struct {
AccessToken string `json:"access_token"`
ExpiresIn float64 `json:"expires_in"`
TokenType string `json:"token_type"`
func Login(client cluster.HTTPClientI, username, password string) (*LoginResp, error) {
loginReq := map[string]string{
"username": username,
"password": password,
}
respStr, err := subnetPostReq(client, subnetLoginURL(), loginReq, nil)
if err != nil {
return nil, err
}
mfaRequired := gjson.Get(respStr, "mfa_required").Bool()
if mfaRequired {
mfaToken := gjson.Get(respStr, "mfa_token").String()
if mfaToken == "" {
return nil, errors.New("missing mfa token")
}
return &LoginResp{AccessToken: "", MfaToken: mfaToken}, nil
}
token := gjson.Get(respStr, "token_info.access_token")
if token.Exists() {
return &LoginResp{AccessToken: token.String(), MfaToken: ""}, nil
}
return nil, errors.New("access token not found in response")
}
// subnetLoginResponse body resonse from subnet after login
type subnetLoginResponse struct {
HasMembership bool `json:"has_memberships"`
TokenInfo tokenInfo `json:"token_info"`
func GetOrganizations(client cluster.HTTPClientI, token string) ([]*models.SubnetOrganization, error) {
headers := subnetAuthHeaders(token)
respStr, err := subnetGetReq(client, subnetOrgsURL(), headers)
if err != nil {
return nil, err
}
var organizations []*models.SubnetOrganization
if err = json.Unmarshal([]byte(respStr), &organizations); err != nil {
return nil, err
}
return organizations, nil
}
// LicenseMetadata claims in subnet license
type LicenseMetadata struct {
Email string `json:"email"`
Issuer string `json:"issuer"`
TeamName string `json:"teamName"`
ServiceType string `json:"serviceType"`
RequestedAt string `json:"requestedAt"`
ExpiresAt string `json:"expiresAt"`
AccountID int64 `json:"accountId"`
Capacity int64 `json:"capacity"`
type LicenseTokenConfig struct {
APIKey string
License string
Proxy string
}
// subnetLicenseResponse body response returned by subnet license endpoint
type subnetLicenseResponse struct {
License string `json:"license"`
Metadata LicenseMetadata `json:"metadata"`
func Register(client cluster.HTTPClientI, admInfo madmin.InfoMessage, apiKey, token, accountID string) (*LicenseTokenConfig, error) {
var headers map[string]string
regInfo := GetClusterRegInfo(admInfo)
regURL := subnetRegisterURL()
if apiKey != "" {
regURL += "?api_key=" + apiKey
} else {
if accountID == "" || token == "" {
return nil, errors.New("missing accountID or authentication token")
}
headers = subnetAuthHeaders(token)
regURL += "?aid=" + accountID
}
regToken, err := GenerateRegToken(regInfo)
if err != nil {
return nil, err
}
reqPayload := mc.ClusterRegistrationReq{Token: regToken}
resp, err := subnetPostReq(client, regURL, reqPayload, headers)
if err != nil {
return nil, err
}
respJSON := gjson.Parse(resp)
subnetAPIKey := respJSON.Get("api_key").String()
licenseJwt := respJSON.Get("license").String()
if subnetAPIKey != "" || licenseJwt != "" {
return &LicenseTokenConfig{
APIKey: subnetAPIKey,
License: licenseJwt,
}, nil
}
return nil, errors.New("subnet api key not found")
}
// subnetLoginRequest body request for subnet login
type subnetRefreshRequest struct {
License string `json:"license"`
}
// getNewLicenseFromExistingLicense will perform license refresh based on the provided license key
func getNewLicenseFromExistingLicense(client cluster.HTTPClientI, licenseKey string) (string, error) {
request := subnetRefreshRequest{
License: licenseKey,
}
// http body for login request
payloadBytes, err := json.Marshal(request)
if err != nil {
return "", err
}
subnetURL := GetSubnetURL()
url := fmt.Sprintf("%s%s", subnetURL, refreshLicenseKeyEndpoint)
resp, err := client.Post(url, "application/json", bytes.NewReader(payloadBytes))
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
subnetLicense := &subnetLicenseResponse{}
// Parse subnet login response
err = json.Unmarshal(bodyBytes, subnetLicense)
if err != nil {
return "", err
}
return subnetLicense.License, nil
}
// getLicenseFromCredentials will perform authentication against subnet using
// user provided credentials and return the current subnet license key
func getLicenseFromCredentials(client cluster.HTTPClientI, username, password string) (string, error) {
request := subnetLoginRequest{
Username: username,
Password: password,
}
// http body for login request
payloadBytes, err := json.Marshal(request)
if err != nil {
return "", err
}
subnetURL := GetSubnetURL()
url := fmt.Sprintf("%s%s", subnetURL, loginEndpoint)
// Authenticate against subnet using email/password provided by user
resp, err := client.Post(url, "application/json", bytes.NewReader(payloadBytes))
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
subnetSession := &subnetLoginResponse{}
// Parse subnet login response
err = json.Unmarshal(bodyBytes, subnetSession)
if err != nil {
return "", err
}
// Get license key using session token
token := subnetSession.TokenInfo.AccessToken
url = fmt.Sprintf("%s%s", subnetURL, licenseKeyEndpoint)
req, err := http.NewRequest("POST", url, nil)
if err != nil {
return "", err
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))
resp, err = client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err = ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return "", fmt.Errorf("subnet served returned status %d code", resp.StatusCode)
}
userLicense := &subnetLicenseResponse{}
// Parse subnet license response
err = json.Unmarshal(bodyBytes, userLicense)
if err != nil {
return "", err
}
return userLicense.License, nil
}
const publicKey = "/downloads/license-pubkey.pem"
// downloadSubnetPublicKey will download the current subnet public key.
func downloadSubnetPublicKey(client cluster.HTTPClientI) (string, error) {
// Get the public key directly from Subnet
url := fmt.Sprintf("%s%s", GetSubnetURL(), publicKey)
url := fmt.Sprintf("%s%s", subnetBaseURL(), publicKey)
resp, err := client.Get(url)
if err != nil {
return "", err
@@ -179,21 +144,10 @@ func downloadSubnetPublicKey(client cluster.HTTPClientI) (string, error) {
return buf.String(), err
}
// ValidateLicense will download the current subnet public key, if the public key its not available for license
// verification then console will fall back to verification with hardcoded public keys
func ValidateLicense(client cluster.HTTPClientI, licenseKey, email, password string) (licInfo *licverifier.LicenseInfo, license string, err error) {
// ParseLicense parses the license with the bundle public key and return it's information
func ParseLicense(client cluster.HTTPClientI, license string) (*licverifier.LicenseInfo, error) {
var publicKeys []string
if email != "" && password != "" {
// fetch subnet license key using user credentials
license, err = getLicenseFromCredentials(client, email, password)
if err != nil {
return nil, "", err
}
} else if licenseKey != "" {
license = licenseKey
} else {
return nil, "", errors.New("invalid license")
}
subnetPubKey, err := downloadSubnetPublicKey(client)
if err != nil {
log.Print(err)
@@ -203,24 +157,11 @@ func ValidateLicense(client cluster.HTTPClientI, licenseKey, email, password str
} else {
publicKeys = append(publicKeys, subnetPubKey)
}
licInfo, err = GetLicenseInfoFromJWT(license, publicKeys)
if err != nil {
return nil, "", err
}
return licInfo, license, nil
}
func RefreshLicense(client cluster.HTTPClientI, licenseKey string) (licInfo *licverifier.LicenseInfo, license string, err error) {
if licenseKey != "" {
license, err = getNewLicenseFromExistingLicense(client, licenseKey)
if err != nil {
return nil, "", err
}
licenseInfo, rawLicense, err := ValidateLicense(client, license, "", "")
if err != nil {
return nil, "", err
}
return licenseInfo, rawLicense, nil
licenseInfo, err := GetLicenseInfoFromJWT(license, publicKeys)
if err != nil {
return nil, err
}
return nil, "", errors.New("invalid license")
return licenseInfo, nil
}

View File

@@ -1,332 +0,0 @@
// This file is part of MinIO Kubernetes Cloud
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package subnet
import (
"bytes"
"io"
"io/ioutil"
"net/http"
"strings"
"testing"
"errors"
)
var HTTPGetMock func(url string) (resp *http.Response, err error)
var HTTPPostMock func(url, contentType string, body io.Reader) (resp *http.Response, err error)
var HTTPDoMock func(req *http.Request) (*http.Response, error)
type HTTPClientMock struct {
Client *http.Client
}
func (c *HTTPClientMock) Get(url string) (resp *http.Response, err error) {
return HTTPGetMock(url)
}
func (c *HTTPClientMock) Post(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return HTTPPostMock(url, contentType, body)
}
func (c *HTTPClientMock) Do(req *http.Request) (*http.Response, error) {
return HTTPDoMock(req)
}
func Test_getLicenseFromCredentials(t *testing.T) {
// HTTP Client mock
clientMock := HTTPClientMock{
Client: &http.Client{},
}
type args struct {
client HTTPClientMock
username string
password string
}
tests := []struct {
name string
args args
want string
wantErr bool
mockFunc func()
}{
{
name: "error when login against subnet",
args: args{
client: clientMock,
username: "invalid",
password: "invalid",
},
want: "",
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error because of malformed subnet response",
args: args{
client: clientMock,
username: "invalid",
password: "invalid",
},
want: "",
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil
}
},
},
{
name: "error when obtaining license from subnet",
args: args{
client: clientMock,
username: "valid",
password: "valid",
},
want: "",
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
// returning test jwt token
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}")))}, nil
}
HTTPDoMock = func(req *http.Request) (*http.Response, error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error when obtaining license from subnet because of malformed response",
args: args{
client: clientMock,
username: "valid",
password: "valid",
},
want: "",
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
// returning test jwt token
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}")))}, nil
}
HTTPDoMock = func(req *http.Request) (*http.Response, error) {
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil
}
},
},
{
name: "license obtained successfully",
args: args{
client: clientMock,
username: "valid",
password: "valid",
},
want: license,
wantErr: false,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
// returning test jwt token
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"has_memberships\":true,\"token_info\":{\"access_token\":\"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik4wRXdOa1V5UXpORU1UUkNOekU0UmpSR1JVWkJSa1UxUmtZNE9EY3lOekZHTXpjNU1qZ3hNZyJ9.eyJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2dyb3VwcyI6W10sImh0dHBzOi8vaWQuc3VibmV0Lm1pbi5pby9jbGFpbXMvcm9sZXMiOltdLCJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vY2xhaW1zL2VtYWlsIjoibGVuaW4rYzFAbWluaW8uaW8iLCJpc3MiOiJodHRwczovL2lkLnN1Ym5ldC5taW4uaW8vIiwic3ViIjoiYXV0aDB8NWZjZWFlYTMyNTNhZjEwMDc3NDZkMDM0IiwiYXVkIjoiaHR0cHM6Ly9zdWJuZXQubWluLmlvL2FwaSIsImlhdCI6MTYwODQxNjE5NiwiZXhwIjoxNjExMDA4MTk2LCJhenAiOiI1WTA0eVZlejNiOFgxUFVzRHVqSmxuZXVuY3ExVjZxaiIsInNjb3BlIjoib2ZmbGluZV9hY2Nlc3MiLCJndHkiOiJwYXNzd29yZCJ9.GC8DRLT0jUEteuBZBmyMXMswLSblCr_89Gu5NcVRUzKSYAaZ5VFW4UFgo1BpiC0sePuWJ0Vykitphx7znTfZfj5B3mZbOw3ejG6kxz7nm9DuYMmySJFYnwroZ9EP02vkW7-n_-YvEg8le1wXfkJ3lTUzO3aWddS4rfQRsZ2YJJUj61GiNyEK_QNP4PrYOuzLyD1wV75NejFqfcFoj7nRkT1K2BM0-89-_f2AFDGTjov6Ig6s1s-zLC9wxcYSmubNwpCJytZmQgPqIepOr065Y6OB4n0n0B5sXguuGuzb8VAkECrHhHPz8ta926fc0jC4XxVCNKdbV1_qC3-1yY7AJA\",\"expires_in\":2592000.0,\"token_type\":\"Bearer\"}}"))),
}, nil
}
HTTPDoMock = func(req *http.Request) (*http.Response, error) {
// returning test jwt license
return &http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"license\":\"" + license + "\",\"metadata\":{\"email\":\"lenin+c1@minio.io\",\"issuer\":\"subnet@minio.io\",\"accountId\":176,\"teamName\":\"console-customer\",\"serviceType\":\"STANDARD\",\"capacity\":25,\"requestedAt\":\"2020-12-19T22:23:31.609144732Z\",\"expiresAt\":\"2021-12-19T22:23:31.609144732Z\"}}"))),
}, nil
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
got, err := getLicenseFromCredentials(&tt.args.client, tt.args.username, tt.args.password)
if (err != nil) != tt.wantErr {
t.Errorf("getLicenseFromCredentials() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("getLicenseFromCredentials() got = %v, want %v", got, tt.want)
}
})
}
}
func Test_downloadSubnetPublicKey(t *testing.T) {
// HTTP Client mock
clientMock := HTTPClientMock{
Client: &http.Client{},
}
type args struct {
client HTTPClientMock
}
tests := []struct {
name string
args args
want string
wantErr bool
mockFunc func()
}{
{
name: "error downloading public key",
args: args{
client: clientMock,
},
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return nil, errors.New("something went wrong")
}
},
wantErr: true,
want: "",
},
{
name: "public key download successfully",
args: args{
client: clientMock,
},
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(bytes.NewReader([]byte("foo")))}, nil
}
},
wantErr: false,
want: "foo",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
got, err := downloadSubnetPublicKey(&tt.args.client)
if (err != nil) != tt.wantErr {
t.Errorf("downloadSubnetPublicKey() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("downloadSubnetPublicKey() got = %v, want %v", got, tt.want)
}
})
}
}
func TestValidateLicense(t *testing.T) {
// HTTP Client mock
clientMock := HTTPClientMock{
Client: &http.Client{},
}
type args struct {
client HTTPClientMock
licenseKey string
email string
password string
}
tests := []struct {
name string
args args
wantLicense string
wantErr bool
mockFunc func()
}{
{
name: "error because nor license nor user or password was provided",
args: args{
client: clientMock,
licenseKey: "",
email: "",
password: "",
},
wantErr: true,
},
{
name: "error because could not get license from credentials",
args: args{
client: clientMock,
licenseKey: "",
email: "email",
password: "password",
},
wantErr: true,
mockFunc: func() {
HTTPPostMock = func(url, contentType string, body io.Reader) (resp *http.Response, err error) {
return nil, errors.New("something went wrong")
}
},
},
{
name: "error because invalid license",
args: args{
client: clientMock,
licenseKey: "invalid license",
email: "",
password: "",
},
wantErr: true,
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(strings.NewReader(publicKeys[0]))}, nil
}
},
},
{
name: "license validated successfully",
args: args{
client: clientMock,
licenseKey: license,
email: "",
password: "",
},
wantErr: false,
mockFunc: func() {
HTTPGetMock = func(url string) (resp *http.Response, err error) {
return &http.Response{Body: ioutil.NopCloser(strings.NewReader(publicKeys[0]))}, nil
}
},
wantLicense: license,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.mockFunc != nil {
tt.mockFunc()
}
_, gotLicense, err := ValidateLicense(&tt.args.client, tt.args.licenseKey, tt.args.email, tt.args.password)
if !tt.wantErr {
t.Skip() // FIXME: fix all success cases
}
if (err != nil) != tt.wantErr {
t.Errorf("ValidateLicense() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotLicense != tt.wantLicense {
t.Errorf("ValidateLicense() gotLicense = %v, want %v", gotLicense, tt.wantLicense)
}
})
}
}

162
pkg/subnet/utils.go Normal file
View File

@@ -0,0 +1,162 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package subnet
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"github.com/minio/console/cluster"
"github.com/minio/madmin-go"
mc "github.com/minio/mc/cmd"
"github.com/minio/pkg/env"
)
const (
subnetRespBodyLimit = 1 << 20 // 1 MiB
)
func subnetBaseURL() string {
return env.Get(ConsoleSubnetURL, "https://subnet.min.io")
}
func subnetRegisterURL() string {
return subnetBaseURL() + "/api/cluster/register"
}
func subnetLoginURL() string {
return subnetBaseURL() + "/api/auth/login"
}
func subnetOrgsURL() string {
return subnetBaseURL() + "/api/auth/organizations"
}
func subnetMFAURL() string {
return subnetBaseURL() + "/api/auth/mfa-login"
}
func GenerateRegToken(clusterRegInfo mc.ClusterRegistrationInfo) (string, error) {
token, e := json.Marshal(clusterRegInfo)
if e != nil {
return "", e
}
return base64.StdEncoding.EncodeToString(token), nil
}
func subnetAuthHeaders(authToken string) map[string]string {
return map[string]string{"Authorization": "Bearer " + authToken}
}
func httpDo(client cluster.HTTPClientI, req *http.Request) (*http.Response, error) {
return client.Do(req)
}
func subnetReqDo(client cluster.HTTPClientI, r *http.Request, headers map[string]string) (string, error) {
for k, v := range headers {
r.Header.Add(k, v)
}
ct := r.Header.Get("Content-Type")
if len(ct) == 0 {
r.Header.Add("Content-Type", "application/json")
}
resp, e := httpDo(client, r)
if e != nil {
return "", e
}
defer resp.Body.Close()
respBytes, e := ioutil.ReadAll(io.LimitReader(resp.Body, subnetRespBodyLimit))
if e != nil {
return "", e
}
respStr := string(respBytes)
if resp.StatusCode == http.StatusOK {
return respStr, nil
}
return respStr, fmt.Errorf("Request failed with code %d and error: %s", resp.StatusCode, respStr)
}
func subnetGetReq(client cluster.HTTPClientI, reqURL string, headers map[string]string) (string, error) {
r, e := http.NewRequest(http.MethodGet, reqURL, nil)
if e != nil {
return "", e
}
return subnetReqDo(client, r, headers)
}
func subnetPostReq(client cluster.HTTPClientI, reqURL string, payload interface{}, headers map[string]string) (string, error) {
body, e := json.Marshal(payload)
if e != nil {
return "", e
}
r, e := http.NewRequest(http.MethodPost, reqURL, bytes.NewReader(body))
if e != nil {
return "", e
}
return subnetReqDo(client, r, headers)
}
func GetClusterRegInfo(admInfo madmin.InfoMessage) mc.ClusterRegistrationInfo {
noOfPools := 1
noOfDrives := 0
for _, srvr := range admInfo.Servers {
if srvr.PoolNumber > noOfPools {
noOfPools = srvr.PoolNumber
}
noOfDrives += len(srvr.Disks)
}
totalSpace, usedSpace := getDriveSpaceInfo(admInfo)
return mc.ClusterRegistrationInfo{
DeploymentID: admInfo.DeploymentID,
ClusterName: admInfo.DeploymentID,
UsedCapacity: admInfo.Usage.Size,
Info: mc.ClusterInfo{
MinioVersion: admInfo.Servers[0].Version,
NoOfServerPools: noOfPools,
NoOfServers: len(admInfo.Servers),
NoOfDrives: noOfDrives,
TotalDriveSpace: totalSpace,
UsedDriveSpace: usedSpace,
NoOfBuckets: admInfo.Buckets.Count,
NoOfObjects: admInfo.Objects.Count,
},
}
}
func getDriveSpaceInfo(admInfo madmin.InfoMessage) (uint64, uint64) {
total := uint64(0)
used := uint64(0)
for _, srvr := range admInfo.Servers {
for _, d := range srvr.Disks {
total += d.TotalSpace
used += d.UsedSpace
}
}
return total, used
}

View File

@@ -1,7 +1,18 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// All rights reserved
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package utils

View File

@@ -1,3 +1,19 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package portalui
import "embed"

View File

@@ -1,315 +1,321 @@
{
"files": {
"main.css": "./static/css/main.c4c1effe.css",
"main.js": "./static/js/main.e413bbf1.js",
"static/js/2178.b2fb43c5.chunk.js": "./static/js/2178.b2fb43c5.chunk.js",
"main.js": "./static/js/main.c7ee1a1d.js",
"static/js/2178.cf1507fb.chunk.js": "./static/js/2178.cf1507fb.chunk.js",
"static/js/5282.e51b8c66.chunk.js": "./static/js/5282.e51b8c66.chunk.js",
"static/js/2818.d1afde1a.chunk.js": "./static/js/2818.d1afde1a.chunk.js",
"static/js/9560.e48c4920.chunk.js": "./static/js/9560.e48c4920.chunk.js",
"static/js/9661.f854f848.chunk.js": "./static/js/9661.f854f848.chunk.js",
"static/js/9330.3d1e16a2.chunk.js": "./static/js/9330.3d1e16a2.chunk.js",
"static/js/4869.3c1ecd5f.chunk.js": "./static/js/4869.3c1ecd5f.chunk.js",
"static/js/2202.2a63896a.chunk.js": "./static/js/2202.2a63896a.chunk.js",
"static/js/7436.1b42daaf.chunk.js": "./static/js/7436.1b42daaf.chunk.js",
"static/js/1056.c8f0b4b3.chunk.js": "./static/js/1056.c8f0b4b3.chunk.js",
"static/js/9779.77d5e14d.chunk.js": "./static/js/9779.77d5e14d.chunk.js",
"static/js/3617.90158304.chunk.js": "./static/js/3617.90158304.chunk.js",
"static/js/7274.2c258b85.chunk.js": "./static/js/7274.2c258b85.chunk.js",
"static/js/7842.8d7284b8.chunk.js": "./static/js/7842.8d7284b8.chunk.js",
"static/js/4745.d734a779.chunk.js": "./static/js/4745.d734a779.chunk.js",
"static/js/4515.605510ac.chunk.js": "./static/js/4515.605510ac.chunk.js",
"static/js/8259.2a0bb90a.chunk.js": "./static/js/8259.2a0bb90a.chunk.js",
"static/js/4839.d6536e1e.chunk.js": "./static/js/4839.d6536e1e.chunk.js",
"static/js/3830.31b98398.chunk.js": "./static/js/3830.31b98398.chunk.js",
"static/js/9275.914f5812.chunk.js": "./static/js/9275.914f5812.chunk.js",
"static/js/8190.a6c60008.chunk.js": "./static/js/8190.a6c60008.chunk.js",
"static/js/7456.fa86b441.chunk.js": "./static/js/7456.fa86b441.chunk.js",
"static/css/6696.8656472c.chunk.css": "./static/css/6696.8656472c.chunk.css",
"static/js/6696.e69732dc.chunk.js": "./static/js/6696.e69732dc.chunk.js",
"static/js/2699.4452ed45.chunk.js": "./static/js/2699.4452ed45.chunk.js",
"static/js/5808.8d639748.chunk.js": "./static/js/5808.8d639748.chunk.js",
"static/js/1237.7ae28fb9.chunk.js": "./static/js/1237.7ae28fb9.chunk.js",
"static/js/8503.fe4b38b4.chunk.js": "./static/js/8503.fe4b38b4.chunk.js",
"static/js/694.043866cc.chunk.js": "./static/js/694.043866cc.chunk.js",
"static/css/9807.8656472c.chunk.css": "./static/css/9807.8656472c.chunk.css",
"static/js/9807.82005fd9.chunk.js": "./static/js/9807.82005fd9.chunk.js",
"static/js/3806.71c34692.chunk.js": "./static/js/3806.71c34692.chunk.js",
"static/js/8661.9398e9f6.chunk.js": "./static/js/8661.9398e9f6.chunk.js",
"static/js/4237.2a675682.chunk.js": "./static/js/4237.2a675682.chunk.js",
"static/js/4577.29aab471.chunk.js": "./static/js/4577.29aab471.chunk.js",
"static/js/5947.c28cc807.chunk.js": "./static/js/5947.c28cc807.chunk.js",
"static/js/4533.97fb0634.chunk.js": "./static/js/4533.97fb0634.chunk.js",
"static/js/6147.e363cc92.chunk.js": "./static/js/6147.e363cc92.chunk.js",
"static/js/2805.09ce1581.chunk.js": "./static/js/2805.09ce1581.chunk.js",
"static/js/1409.ce8091c2.chunk.js": "./static/js/1409.ce8091c2.chunk.js",
"static/js/9560.6ff6a758.chunk.js": "./static/js/9560.6ff6a758.chunk.js",
"static/js/4247.636fee85.chunk.js": "./static/js/4247.636fee85.chunk.js",
"static/js/9330.5c857d6b.chunk.js": "./static/js/9330.5c857d6b.chunk.js",
"static/js/4869.b14a3a6c.chunk.js": "./static/js/4869.b14a3a6c.chunk.js",
"static/js/2202.516dc033.chunk.js": "./static/js/2202.516dc033.chunk.js",
"static/js/7436.17e2bbf0.chunk.js": "./static/js/7436.17e2bbf0.chunk.js",
"static/js/1056.6f1cb8e0.chunk.js": "./static/js/1056.6f1cb8e0.chunk.js",
"static/js/9779.0e8be0cc.chunk.js": "./static/js/9779.0e8be0cc.chunk.js",
"static/js/3617.38600ea7.chunk.js": "./static/js/3617.38600ea7.chunk.js",
"static/js/7274.64442dfa.chunk.js": "./static/js/7274.64442dfa.chunk.js",
"static/js/7842.6ec32dd7.chunk.js": "./static/js/7842.6ec32dd7.chunk.js",
"static/js/4745.00dcce39.chunk.js": "./static/js/4745.00dcce39.chunk.js",
"static/js/4515.fc843c16.chunk.js": "./static/js/4515.fc843c16.chunk.js",
"static/js/8259.d34a4da6.chunk.js": "./static/js/8259.d34a4da6.chunk.js",
"static/js/4839.cfc74e75.chunk.js": "./static/js/4839.cfc74e75.chunk.js",
"static/js/8853.b298ca0c.chunk.js": "./static/js/8853.b298ca0c.chunk.js",
"static/js/9275.3648f309.chunk.js": "./static/js/9275.3648f309.chunk.js",
"static/js/3795.ca99e16b.chunk.js": "./static/js/3795.ca99e16b.chunk.js",
"static/js/7314.1fb79e31.chunk.js": "./static/js/7314.1fb79e31.chunk.js",
"static/js/7456.99d2d848.chunk.js": "./static/js/7456.99d2d848.chunk.js",
"static/css/5673.ce92c9f5.chunk.css": "./static/css/5673.ce92c9f5.chunk.css",
"static/js/5673.d3eb036c.chunk.js": "./static/js/5673.d3eb036c.chunk.js",
"static/js/2699.ac935aa8.chunk.js": "./static/js/2699.ac935aa8.chunk.js",
"static/js/5808.5e176e4d.chunk.js": "./static/js/5808.5e176e4d.chunk.js",
"static/js/1237.f11be969.chunk.js": "./static/js/1237.f11be969.chunk.js",
"static/js/8503.6ce0ff69.chunk.js": "./static/js/8503.6ce0ff69.chunk.js",
"static/js/6615.f26600a0.chunk.js": "./static/js/6615.f26600a0.chunk.js",
"static/css/9807.ce92c9f5.chunk.css": "./static/css/9807.ce92c9f5.chunk.css",
"static/js/9807.3f033821.chunk.js": "./static/js/9807.3f033821.chunk.js",
"static/js/3806.e2daceb3.chunk.js": "./static/js/3806.e2daceb3.chunk.js",
"static/js/2742.f1b14cdb.chunk.js": "./static/js/2742.f1b14cdb.chunk.js",
"static/js/3474.378d2159.chunk.js": "./static/js/3474.378d2159.chunk.js",
"static/js/4577.ee518ba9.chunk.js": "./static/js/4577.ee518ba9.chunk.js",
"static/js/5947.88f9751e.chunk.js": "./static/js/5947.88f9751e.chunk.js",
"static/js/4533.e1a532b6.chunk.js": "./static/js/4533.e1a532b6.chunk.js",
"static/js/6147.aed56e85.chunk.js": "./static/js/6147.aed56e85.chunk.js",
"static/js/2805.73cec790.chunk.js": "./static/js/2805.73cec790.chunk.js",
"static/js/3143.e6a9621d.chunk.js": "./static/js/3143.e6a9621d.chunk.js",
"static/js/428.092071b6.chunk.js": "./static/js/428.092071b6.chunk.js",
"static/js/1069.4329a890.chunk.js": "./static/js/1069.4329a890.chunk.js",
"static/js/1140.bc981889.chunk.js": "./static/js/1140.bc981889.chunk.js",
"static/js/2094.87c79d6e.chunk.js": "./static/js/2094.87c79d6e.chunk.js",
"static/js/7950.8cccfb81.chunk.js": "./static/js/7950.8cccfb81.chunk.js",
"static/js/8369.91bb6d80.chunk.js": "./static/js/8369.91bb6d80.chunk.js",
"static/js/8961.4409f5d0.chunk.js": "./static/js/8961.4409f5d0.chunk.js",
"static/js/3967.ead99d49.chunk.js": "./static/js/3967.ead99d49.chunk.js",
"static/css/5677.8656472c.chunk.css": "./static/css/5677.8656472c.chunk.css",
"static/js/5677.84696c79.chunk.js": "./static/js/5677.84696c79.chunk.js",
"static/css/4360.8656472c.chunk.css": "./static/css/4360.8656472c.chunk.css",
"static/js/4360.128ebb74.chunk.js": "./static/js/4360.128ebb74.chunk.js",
"static/js/7664.5db3e36a.chunk.js": "./static/js/7664.5db3e36a.chunk.js",
"static/js/9080.f4810646.chunk.js": "./static/js/9080.f4810646.chunk.js",
"static/js/5961.494f0900.chunk.js": "./static/js/5961.494f0900.chunk.js",
"static/js/428.b5539f2b.chunk.js": "./static/js/428.b5539f2b.chunk.js",
"static/js/1069.6dfe4ad0.chunk.js": "./static/js/1069.6dfe4ad0.chunk.js",
"static/js/1140.0208375a.chunk.js": "./static/js/1140.0208375a.chunk.js",
"static/js/2094.4854485e.chunk.js": "./static/js/2094.4854485e.chunk.js",
"static/js/7950.455e5528.chunk.js": "./static/js/7950.455e5528.chunk.js",
"static/js/671.18a32329.chunk.js": "./static/js/671.18a32329.chunk.js",
"static/js/8961.8aa802d6.chunk.js": "./static/js/8961.8aa802d6.chunk.js",
"static/js/3967.ef703755.chunk.js": "./static/js/3967.ef703755.chunk.js",
"static/css/5677.ce92c9f5.chunk.css": "./static/css/5677.ce92c9f5.chunk.css",
"static/js/5677.654039a6.chunk.js": "./static/js/5677.654039a6.chunk.js",
"static/css/4360.ce92c9f5.chunk.css": "./static/css/4360.ce92c9f5.chunk.css",
"static/js/4360.bbbd8faf.chunk.js": "./static/js/4360.bbbd8faf.chunk.js",
"static/js/4109.04a89c2d.chunk.js": "./static/js/4109.04a89c2d.chunk.js",
"static/js/9080.bb419916.chunk.js": "./static/js/9080.bb419916.chunk.js",
"static/js/5961.04fd21a3.chunk.js": "./static/js/5961.04fd21a3.chunk.js",
"static/js/6549.e0686e32.chunk.js": "./static/js/6549.e0686e32.chunk.js",
"static/js/7498.5bdce2df.chunk.js": "./static/js/7498.5bdce2df.chunk.js",
"static/js/9421.fcfffa57.chunk.js": "./static/js/9421.fcfffa57.chunk.js",
"static/js/5284.e972df04.chunk.js": "./static/js/5284.e972df04.chunk.js",
"static/js/4818.d857d014.chunk.js": "./static/js/4818.d857d014.chunk.js",
"static/js/2401.dab77e89.chunk.js": "./static/js/2401.dab77e89.chunk.js",
"static/css/8724.8656472c.chunk.css": "./static/css/8724.8656472c.chunk.css",
"static/js/8724.8dc525b3.chunk.js": "./static/js/8724.8dc525b3.chunk.js",
"static/js/2182.d27fb965.chunk.js": "./static/js/2182.d27fb965.chunk.js",
"static/js/7764.9f2f6b88.chunk.js": "./static/js/7764.9f2f6b88.chunk.js",
"static/js/4220.7760227f.chunk.js": "./static/js/4220.7760227f.chunk.js",
"static/js/1719.54b40a33.chunk.js": "./static/js/1719.54b40a33.chunk.js",
"static/js/3320.646cbb74.chunk.js": "./static/js/3320.646cbb74.chunk.js",
"static/js/9923.634b0622.chunk.js": "./static/js/9923.634b0622.chunk.js",
"static/js/7498.07dd467b.chunk.js": "./static/js/7498.07dd467b.chunk.js",
"static/js/9421.a88c8e02.chunk.js": "./static/js/9421.a88c8e02.chunk.js",
"static/js/5284.e23070a0.chunk.js": "./static/js/5284.e23070a0.chunk.js",
"static/js/4818.65ba088c.chunk.js": "./static/js/4818.65ba088c.chunk.js",
"static/js/2401.74634660.chunk.js": "./static/js/2401.74634660.chunk.js",
"static/css/8724.ce92c9f5.chunk.css": "./static/css/8724.ce92c9f5.chunk.css",
"static/js/8724.cf8fb340.chunk.js": "./static/js/8724.cf8fb340.chunk.js",
"static/js/2182.468a5b4e.chunk.js": "./static/js/2182.468a5b4e.chunk.js",
"static/js/7764.3b798da3.chunk.js": "./static/js/7764.3b798da3.chunk.js",
"static/js/4220.308c5fb3.chunk.js": "./static/js/4220.308c5fb3.chunk.js",
"static/js/1719.fbaa408a.chunk.js": "./static/js/1719.fbaa408a.chunk.js",
"static/js/3320.2e853989.chunk.js": "./static/js/3320.2e853989.chunk.js",
"static/js/9923.25fa1a75.chunk.js": "./static/js/9923.25fa1a75.chunk.js",
"static/js/9586.c44d8f2b.chunk.js": "./static/js/9586.c44d8f2b.chunk.js",
"static/js/7261.39f723d9.chunk.js": "./static/js/7261.39f723d9.chunk.js",
"static/js/6436.c3cf82d9.chunk.js": "./static/js/6436.c3cf82d9.chunk.js",
"static/js/8343.c498d4af.chunk.js": "./static/js/8343.c498d4af.chunk.js",
"static/js/2841.73580ff7.chunk.js": "./static/js/2841.73580ff7.chunk.js",
"static/js/6167.2cf4273f.chunk.js": "./static/js/6167.2cf4273f.chunk.js",
"static/js/3698.2ea7b498.chunk.js": "./static/js/3698.2ea7b498.chunk.js",
"static/js/1971.52779ae6.chunk.js": "./static/js/1971.52779ae6.chunk.js",
"static/js/7346.5ff362f5.chunk.js": "./static/js/7346.5ff362f5.chunk.js",
"static/js/5144.5d8ad21e.chunk.js": "./static/js/5144.5d8ad21e.chunk.js",
"static/js/5125.2efed100.chunk.js": "./static/js/5125.2efed100.chunk.js",
"static/js/528.c588d7c6.chunk.js": "./static/js/528.c588d7c6.chunk.js",
"static/js/7187.7c4714ee.chunk.js": "./static/js/7187.7c4714ee.chunk.js",
"static/js/6173.3a1318da.chunk.js": "./static/js/6173.3a1318da.chunk.js",
"static/js/7146.77e9b1cc.chunk.js": "./static/js/7146.77e9b1cc.chunk.js",
"static/js/9924.8e40abd7.chunk.js": "./static/js/9924.8e40abd7.chunk.js",
"static/js/9193.6764f72b.chunk.js": "./static/js/9193.6764f72b.chunk.js",
"static/js/7451.0fa5a7e5.chunk.js": "./static/js/7451.0fa5a7e5.chunk.js",
"static/js/6511.9ad8d9a8.chunk.js": "./static/js/6511.9ad8d9a8.chunk.js",
"static/css/165.8656472c.chunk.css": "./static/css/165.8656472c.chunk.css",
"static/js/165.03f5d4c1.chunk.js": "./static/js/165.03f5d4c1.chunk.js",
"static/js/4121.b9b399ef.chunk.js": "./static/js/4121.b9b399ef.chunk.js",
"static/js/609.3f81f01c.chunk.js": "./static/js/609.3f81f01c.chunk.js",
"static/js/2892.1d404509.chunk.js": "./static/js/2892.1d404509.chunk.js",
"static/js/7926.7b87452c.chunk.js": "./static/js/7926.7b87452c.chunk.js",
"static/js/6145.1b8916cd.chunk.js": "./static/js/6145.1b8916cd.chunk.js",
"static/css/6951.8656472c.chunk.css": "./static/css/6951.8656472c.chunk.css",
"static/js/6951.34c85fa1.chunk.js": "./static/js/6951.34c85fa1.chunk.js",
"static/js/2966.3df2cc1f.chunk.js": "./static/js/2966.3df2cc1f.chunk.js",
"static/js/4177.b1d4c761.chunk.js": "./static/js/4177.b1d4c761.chunk.js",
"static/js/9679.3942615d.chunk.js": "./static/js/9679.3942615d.chunk.js",
"static/js/8333.a7249061.chunk.js": "./static/js/8333.a7249061.chunk.js",
"static/js/1711.2e4f085e.chunk.js": "./static/js/1711.2e4f085e.chunk.js",
"static/js/9.40a0afdf.chunk.js": "./static/js/9.40a0afdf.chunk.js",
"static/js/4487.625e74d7.chunk.js": "./static/js/4487.625e74d7.chunk.js",
"static/js/6866.eca0c7d9.chunk.js": "./static/js/6866.eca0c7d9.chunk.js",
"static/js/8564.289a240e.chunk.js": "./static/js/8564.289a240e.chunk.js",
"static/js/3152.4d22e959.chunk.js": "./static/js/3152.4d22e959.chunk.js",
"static/js/7007.7afa6cca.chunk.js": "./static/js/7007.7afa6cca.chunk.js",
"static/js/14.ec5eac10.chunk.js": "./static/js/14.ec5eac10.chunk.js",
"static/js/892.c5842324.chunk.js": "./static/js/892.c5842324.chunk.js",
"static/js/2066.81f72337.chunk.js": "./static/js/2066.81f72337.chunk.js",
"static/js/7261.98082dab.chunk.js": "./static/js/7261.98082dab.chunk.js",
"static/js/6436.f2bab163.chunk.js": "./static/js/6436.f2bab163.chunk.js",
"static/js/8343.b174034e.chunk.js": "./static/js/8343.b174034e.chunk.js",
"static/js/2841.95af1213.chunk.js": "./static/js/2841.95af1213.chunk.js",
"static/js/6167.edad2368.chunk.js": "./static/js/6167.edad2368.chunk.js",
"static/js/3698.49eb7411.chunk.js": "./static/js/3698.49eb7411.chunk.js",
"static/js/1971.306de04b.chunk.js": "./static/js/1971.306de04b.chunk.js",
"static/js/7346.8e455eaa.chunk.js": "./static/js/7346.8e455eaa.chunk.js",
"static/js/5144.f66d75af.chunk.js": "./static/js/5144.f66d75af.chunk.js",
"static/js/5125.9822771e.chunk.js": "./static/js/5125.9822771e.chunk.js",
"static/js/1973.55987b98.chunk.js": "./static/js/1973.55987b98.chunk.js",
"static/js/7187.b3ba71a1.chunk.js": "./static/js/7187.b3ba71a1.chunk.js",
"static/js/6173.b70c4e1a.chunk.js": "./static/js/6173.b70c4e1a.chunk.js",
"static/js/7146.09b3a43c.chunk.js": "./static/js/7146.09b3a43c.chunk.js",
"static/js/9924.a7c06331.chunk.js": "./static/js/9924.a7c06331.chunk.js",
"static/js/9193.cbd8d842.chunk.js": "./static/js/9193.cbd8d842.chunk.js",
"static/js/7451.43cf8bf6.chunk.js": "./static/js/7451.43cf8bf6.chunk.js",
"static/css/165.ce92c9f5.chunk.css": "./static/css/165.ce92c9f5.chunk.css",
"static/js/165.6ba70895.chunk.js": "./static/js/165.6ba70895.chunk.js",
"static/js/4121.17b5e70f.chunk.js": "./static/js/4121.17b5e70f.chunk.js",
"static/js/609.218c57b6.chunk.js": "./static/js/609.218c57b6.chunk.js",
"static/js/3421.aa3eeb04.chunk.js": "./static/js/3421.aa3eeb04.chunk.js",
"static/js/2892.f2ab7fe7.chunk.js": "./static/js/2892.f2ab7fe7.chunk.js",
"static/js/7926.d155ef62.chunk.js": "./static/js/7926.d155ef62.chunk.js",
"static/js/6145.819d854c.chunk.js": "./static/js/6145.819d854c.chunk.js",
"static/css/6951.ce92c9f5.chunk.css": "./static/css/6951.ce92c9f5.chunk.css",
"static/js/6951.e58557fd.chunk.js": "./static/js/6951.e58557fd.chunk.js",
"static/js/2966.c61fbf10.chunk.js": "./static/js/2966.c61fbf10.chunk.js",
"static/js/4177.bfe448c8.chunk.js": "./static/js/4177.bfe448c8.chunk.js",
"static/js/9679.2b8424ba.chunk.js": "./static/js/9679.2b8424ba.chunk.js",
"static/js/8333.b0c9e377.chunk.js": "./static/js/8333.b0c9e377.chunk.js",
"static/js/1711.a046e26c.chunk.js": "./static/js/1711.a046e26c.chunk.js",
"static/js/9.0e073695.chunk.js": "./static/js/9.0e073695.chunk.js",
"static/js/4487.3ad24a2d.chunk.js": "./static/js/4487.3ad24a2d.chunk.js",
"static/js/6866.aa2f03f0.chunk.js": "./static/js/6866.aa2f03f0.chunk.js",
"static/js/8564.11a3023b.chunk.js": "./static/js/8564.11a3023b.chunk.js",
"static/js/3152.daa99377.chunk.js": "./static/js/3152.daa99377.chunk.js",
"static/js/7007.3606231e.chunk.js": "./static/js/7007.3606231e.chunk.js",
"static/js/14.25ecb28d.chunk.js": "./static/js/14.25ecb28d.chunk.js",
"static/js/2066.5180dac0.chunk.js": "./static/js/2066.5180dac0.chunk.js",
"static/js/5444.e8727da9.chunk.js": "./static/js/5444.e8727da9.chunk.js",
"static/js/5594.6e7b403a.chunk.js": "./static/js/5594.6e7b403a.chunk.js",
"static/js/3990.5e998248.chunk.js": "./static/js/3990.5e998248.chunk.js",
"static/js/5399.5699b879.chunk.js": "./static/js/5399.5699b879.chunk.js",
"static/js/892.2aaf58f1.chunk.js": "./static/js/892.2aaf58f1.chunk.js",
"static/js/3690.536fb187.chunk.js": "./static/js/3690.536fb187.chunk.js",
"static/js/5399.6bc650ac.chunk.js": "./static/js/5399.6bc650ac.chunk.js",
"static/js/606.28fdb5bc.chunk.js": "./static/js/606.28fdb5bc.chunk.js",
"static/js/9998.e7c54804.chunk.js": "./static/js/9998.e7c54804.chunk.js",
"static/js/9769.733c21d3.chunk.js": "./static/js/9769.733c21d3.chunk.js",
"static/js/9998.1a613ce5.chunk.js": "./static/js/9998.1a613ce5.chunk.js",
"static/js/9769.a2bfb4d7.chunk.js": "./static/js/9769.a2bfb4d7.chunk.js",
"static/js/8954.00599ff1.chunk.js": "./static/js/8954.00599ff1.chunk.js",
"static/js/7248.1e0c4e19.chunk.js": "./static/js/7248.1e0c4e19.chunk.js",
"static/js/4837.afb48283.chunk.js": "./static/js/4837.afb48283.chunk.js",
"static/js/4837.98af4cfe.chunk.js": "./static/js/4837.98af4cfe.chunk.js",
"static/js/3020.3b2e782c.chunk.js": "./static/js/3020.3b2e782c.chunk.js",
"static/js/9056.ad0432f8.chunk.js": "./static/js/9056.ad0432f8.chunk.js",
"static/js/9056.41f0e489.chunk.js": "./static/js/9056.41f0e489.chunk.js",
"static/js/3360.453f0701.chunk.js": "./static/js/3360.453f0701.chunk.js",
"static/js/7561.81edce87.chunk.js": "./static/js/7561.81edce87.chunk.js",
"static/js/8420.68d5dfb4.chunk.js": "./static/js/8420.68d5dfb4.chunk.js",
"static/js/1420.517f0e88.chunk.js": "./static/js/1420.517f0e88.chunk.js",
"static/js/2309.49be65c5.chunk.js": "./static/js/2309.49be65c5.chunk.js",
"static/js/2364.a32e93a4.chunk.js": "./static/js/2364.a32e93a4.chunk.js",
"static/js/3356.3534f8c0.chunk.js": "./static/js/3356.3534f8c0.chunk.js",
"static/js/1233.9903fc82.chunk.js": "./static/js/1233.9903fc82.chunk.js",
"static/js/3356.05b42758.chunk.js": "./static/js/3356.05b42758.chunk.js",
"static/js/8016.b0ab3b67.chunk.js": "./static/js/8016.b0ab3b67.chunk.js",
"static/js/6851.3b47e1b9.chunk.js": "./static/js/6851.3b47e1b9.chunk.js",
"static/js/2253.0ed59cf3.chunk.js": "./static/js/2253.0ed59cf3.chunk.js",
"static/js/9415.8ad63e79.chunk.js": "./static/js/9415.8ad63e79.chunk.js",
"static/js/1811.cf61844d.chunk.js": "./static/js/1811.cf61844d.chunk.js",
"static/js/7857.cc3da495.chunk.js": "./static/js/7857.cc3da495.chunk.js",
"static/js/3538.1b5c3c94.chunk.js": "./static/js/3538.1b5c3c94.chunk.js",
"static/js/3290.cf0a5a06.chunk.js": "./static/js/3290.cf0a5a06.chunk.js",
"static/js/7348.44810a11.chunk.js": "./static/js/7348.44810a11.chunk.js",
"static/js/2253.d527f443.chunk.js": "./static/js/2253.d527f443.chunk.js",
"static/js/6427.028089e0.chunk.js": "./static/js/6427.028089e0.chunk.js",
"static/js/3538.60e98cef.chunk.js": "./static/js/3538.60e98cef.chunk.js",
"static/js/8749.fa0fb710.chunk.js": "./static/js/8749.fa0fb710.chunk.js",
"static/js/7348.b037b952.chunk.js": "./static/js/7348.b037b952.chunk.js",
"static/js/1760.8589ea77.chunk.js": "./static/js/1760.8589ea77.chunk.js",
"static/js/8708.1735e6e6.chunk.js": "./static/js/8708.1735e6e6.chunk.js",
"static/js/3045.691c66fa.chunk.js": "./static/js/3045.691c66fa.chunk.js",
"static/js/7049.620c3eec.chunk.js": "./static/js/7049.620c3eec.chunk.js",
"static/js/2873.ebf17fc8.chunk.js": "./static/js/2873.ebf17fc8.chunk.js",
"static/js/8708.70b62dd5.chunk.js": "./static/js/8708.70b62dd5.chunk.js",
"static/js/2594.77f64a1f.chunk.js": "./static/js/2594.77f64a1f.chunk.js",
"static/js/440.5bb0a106.chunk.js": "./static/js/440.5bb0a106.chunk.js",
"static/js/3045.f1cc8d42.chunk.js": "./static/js/3045.f1cc8d42.chunk.js",
"static/js/4817.2c511e72.chunk.js": "./static/js/4817.2c511e72.chunk.js",
"static/js/227.5ba677a7.chunk.js": "./static/js/227.5ba677a7.chunk.js",
"static/js/4105.1b1d6fe0.chunk.js": "./static/js/4105.1b1d6fe0.chunk.js",
"static/js/8174.540bd07a.chunk.js": "./static/js/8174.540bd07a.chunk.js",
"static/js/7042.6737005f.chunk.js": "./static/js/7042.6737005f.chunk.js",
"static/js/3201.8afb9fa9.chunk.js": "./static/js/3201.8afb9fa9.chunk.js",
"static/js/1724.30bcf6f8.chunk.js": "./static/js/1724.30bcf6f8.chunk.js",
"static/js/7454.eb7fb0a0.chunk.js": "./static/js/7454.eb7fb0a0.chunk.js",
"static/js/6873.177d160e.chunk.js": "./static/js/6873.177d160e.chunk.js",
"static/js/3453.3c290198.chunk.js": "./static/js/3453.3c290198.chunk.js",
"static/js/938.8f6a5aa1.chunk.js": "./static/js/938.8f6a5aa1.chunk.js",
"static/js/4105.a498ca05.chunk.js": "./static/js/4105.a498ca05.chunk.js",
"static/js/9876.3e5a4f83.chunk.js": "./static/js/9876.3e5a4f83.chunk.js",
"static/js/7777.7ea62662.chunk.js": "./static/js/7777.7ea62662.chunk.js",
"static/js/7042.343b48b2.chunk.js": "./static/js/7042.343b48b2.chunk.js",
"static/js/3201.9f8a570f.chunk.js": "./static/js/3201.9f8a570f.chunk.js",
"static/js/7664.9ba57569.chunk.js": "./static/js/7664.9ba57569.chunk.js",
"static/js/5170.2d3119fa.chunk.js": "./static/js/5170.2d3119fa.chunk.js",
"static/js/7454.6a33b546.chunk.js": "./static/js/7454.6a33b546.chunk.js",
"static/js/6873.a4c0dde3.chunk.js": "./static/js/6873.a4c0dde3.chunk.js",
"static/js/3453.9ef32892.chunk.js": "./static/js/3453.9ef32892.chunk.js",
"index.html": "./index.html",
"main.c4c1effe.css.map": "./static/css/main.c4c1effe.css.map",
"main.e413bbf1.js.map": "./static/js/main.e413bbf1.js.map",
"2178.b2fb43c5.chunk.js.map": "./static/js/2178.b2fb43c5.chunk.js.map",
"main.c7ee1a1d.js.map": "./static/js/main.c7ee1a1d.js.map",
"2178.cf1507fb.chunk.js.map": "./static/js/2178.cf1507fb.chunk.js.map",
"5282.e51b8c66.chunk.js.map": "./static/js/5282.e51b8c66.chunk.js.map",
"2818.d1afde1a.chunk.js.map": "./static/js/2818.d1afde1a.chunk.js.map",
"9560.e48c4920.chunk.js.map": "./static/js/9560.e48c4920.chunk.js.map",
"9661.f854f848.chunk.js.map": "./static/js/9661.f854f848.chunk.js.map",
"9330.3d1e16a2.chunk.js.map": "./static/js/9330.3d1e16a2.chunk.js.map",
"4869.3c1ecd5f.chunk.js.map": "./static/js/4869.3c1ecd5f.chunk.js.map",
"2202.2a63896a.chunk.js.map": "./static/js/2202.2a63896a.chunk.js.map",
"7436.1b42daaf.chunk.js.map": "./static/js/7436.1b42daaf.chunk.js.map",
"1056.c8f0b4b3.chunk.js.map": "./static/js/1056.c8f0b4b3.chunk.js.map",
"9779.77d5e14d.chunk.js.map": "./static/js/9779.77d5e14d.chunk.js.map",
"3617.90158304.chunk.js.map": "./static/js/3617.90158304.chunk.js.map",
"7274.2c258b85.chunk.js.map": "./static/js/7274.2c258b85.chunk.js.map",
"7842.8d7284b8.chunk.js.map": "./static/js/7842.8d7284b8.chunk.js.map",
"4745.d734a779.chunk.js.map": "./static/js/4745.d734a779.chunk.js.map",
"4515.605510ac.chunk.js.map": "./static/js/4515.605510ac.chunk.js.map",
"8259.2a0bb90a.chunk.js.map": "./static/js/8259.2a0bb90a.chunk.js.map",
"4839.d6536e1e.chunk.js.map": "./static/js/4839.d6536e1e.chunk.js.map",
"3830.31b98398.chunk.js.map": "./static/js/3830.31b98398.chunk.js.map",
"9275.914f5812.chunk.js.map": "./static/js/9275.914f5812.chunk.js.map",
"8190.a6c60008.chunk.js.map": "./static/js/8190.a6c60008.chunk.js.map",
"7456.fa86b441.chunk.js.map": "./static/js/7456.fa86b441.chunk.js.map",
"6696.8656472c.chunk.css.map": "./static/css/6696.8656472c.chunk.css.map",
"6696.e69732dc.chunk.js.map": "./static/js/6696.e69732dc.chunk.js.map",
"2699.4452ed45.chunk.js.map": "./static/js/2699.4452ed45.chunk.js.map",
"5808.8d639748.chunk.js.map": "./static/js/5808.8d639748.chunk.js.map",
"1237.7ae28fb9.chunk.js.map": "./static/js/1237.7ae28fb9.chunk.js.map",
"8503.fe4b38b4.chunk.js.map": "./static/js/8503.fe4b38b4.chunk.js.map",
"694.043866cc.chunk.js.map": "./static/js/694.043866cc.chunk.js.map",
"9807.8656472c.chunk.css.map": "./static/css/9807.8656472c.chunk.css.map",
"9807.82005fd9.chunk.js.map": "./static/js/9807.82005fd9.chunk.js.map",
"3806.71c34692.chunk.js.map": "./static/js/3806.71c34692.chunk.js.map",
"8661.9398e9f6.chunk.js.map": "./static/js/8661.9398e9f6.chunk.js.map",
"4237.2a675682.chunk.js.map": "./static/js/4237.2a675682.chunk.js.map",
"4577.29aab471.chunk.js.map": "./static/js/4577.29aab471.chunk.js.map",
"5947.c28cc807.chunk.js.map": "./static/js/5947.c28cc807.chunk.js.map",
"4533.97fb0634.chunk.js.map": "./static/js/4533.97fb0634.chunk.js.map",
"6147.e363cc92.chunk.js.map": "./static/js/6147.e363cc92.chunk.js.map",
"2805.09ce1581.chunk.js.map": "./static/js/2805.09ce1581.chunk.js.map",
"1409.ce8091c2.chunk.js.map": "./static/js/1409.ce8091c2.chunk.js.map",
"9560.6ff6a758.chunk.js.map": "./static/js/9560.6ff6a758.chunk.js.map",
"4247.636fee85.chunk.js.map": "./static/js/4247.636fee85.chunk.js.map",
"9330.5c857d6b.chunk.js.map": "./static/js/9330.5c857d6b.chunk.js.map",
"4869.b14a3a6c.chunk.js.map": "./static/js/4869.b14a3a6c.chunk.js.map",
"2202.516dc033.chunk.js.map": "./static/js/2202.516dc033.chunk.js.map",
"7436.17e2bbf0.chunk.js.map": "./static/js/7436.17e2bbf0.chunk.js.map",
"1056.6f1cb8e0.chunk.js.map": "./static/js/1056.6f1cb8e0.chunk.js.map",
"9779.0e8be0cc.chunk.js.map": "./static/js/9779.0e8be0cc.chunk.js.map",
"3617.38600ea7.chunk.js.map": "./static/js/3617.38600ea7.chunk.js.map",
"7274.64442dfa.chunk.js.map": "./static/js/7274.64442dfa.chunk.js.map",
"7842.6ec32dd7.chunk.js.map": "./static/js/7842.6ec32dd7.chunk.js.map",
"4745.00dcce39.chunk.js.map": "./static/js/4745.00dcce39.chunk.js.map",
"4515.fc843c16.chunk.js.map": "./static/js/4515.fc843c16.chunk.js.map",
"8259.d34a4da6.chunk.js.map": "./static/js/8259.d34a4da6.chunk.js.map",
"4839.cfc74e75.chunk.js.map": "./static/js/4839.cfc74e75.chunk.js.map",
"8853.b298ca0c.chunk.js.map": "./static/js/8853.b298ca0c.chunk.js.map",
"9275.3648f309.chunk.js.map": "./static/js/9275.3648f309.chunk.js.map",
"3795.ca99e16b.chunk.js.map": "./static/js/3795.ca99e16b.chunk.js.map",
"7314.1fb79e31.chunk.js.map": "./static/js/7314.1fb79e31.chunk.js.map",
"7456.99d2d848.chunk.js.map": "./static/js/7456.99d2d848.chunk.js.map",
"5673.ce92c9f5.chunk.css.map": "./static/css/5673.ce92c9f5.chunk.css.map",
"5673.d3eb036c.chunk.js.map": "./static/js/5673.d3eb036c.chunk.js.map",
"2699.ac935aa8.chunk.js.map": "./static/js/2699.ac935aa8.chunk.js.map",
"5808.5e176e4d.chunk.js.map": "./static/js/5808.5e176e4d.chunk.js.map",
"1237.f11be969.chunk.js.map": "./static/js/1237.f11be969.chunk.js.map",
"8503.6ce0ff69.chunk.js.map": "./static/js/8503.6ce0ff69.chunk.js.map",
"6615.f26600a0.chunk.js.map": "./static/js/6615.f26600a0.chunk.js.map",
"9807.ce92c9f5.chunk.css.map": "./static/css/9807.ce92c9f5.chunk.css.map",
"9807.3f033821.chunk.js.map": "./static/js/9807.3f033821.chunk.js.map",
"3806.e2daceb3.chunk.js.map": "./static/js/3806.e2daceb3.chunk.js.map",
"2742.f1b14cdb.chunk.js.map": "./static/js/2742.f1b14cdb.chunk.js.map",
"3474.378d2159.chunk.js.map": "./static/js/3474.378d2159.chunk.js.map",
"4577.ee518ba9.chunk.js.map": "./static/js/4577.ee518ba9.chunk.js.map",
"5947.88f9751e.chunk.js.map": "./static/js/5947.88f9751e.chunk.js.map",
"4533.e1a532b6.chunk.js.map": "./static/js/4533.e1a532b6.chunk.js.map",
"6147.aed56e85.chunk.js.map": "./static/js/6147.aed56e85.chunk.js.map",
"2805.73cec790.chunk.js.map": "./static/js/2805.73cec790.chunk.js.map",
"3143.e6a9621d.chunk.js.map": "./static/js/3143.e6a9621d.chunk.js.map",
"428.092071b6.chunk.js.map": "./static/js/428.092071b6.chunk.js.map",
"1069.4329a890.chunk.js.map": "./static/js/1069.4329a890.chunk.js.map",
"1140.bc981889.chunk.js.map": "./static/js/1140.bc981889.chunk.js.map",
"2094.87c79d6e.chunk.js.map": "./static/js/2094.87c79d6e.chunk.js.map",
"7950.8cccfb81.chunk.js.map": "./static/js/7950.8cccfb81.chunk.js.map",
"8369.91bb6d80.chunk.js.map": "./static/js/8369.91bb6d80.chunk.js.map",
"8961.4409f5d0.chunk.js.map": "./static/js/8961.4409f5d0.chunk.js.map",
"3967.ead99d49.chunk.js.map": "./static/js/3967.ead99d49.chunk.js.map",
"5677.8656472c.chunk.css.map": "./static/css/5677.8656472c.chunk.css.map",
"5677.84696c79.chunk.js.map": "./static/js/5677.84696c79.chunk.js.map",
"4360.8656472c.chunk.css.map": "./static/css/4360.8656472c.chunk.css.map",
"4360.128ebb74.chunk.js.map": "./static/js/4360.128ebb74.chunk.js.map",
"7664.5db3e36a.chunk.js.map": "./static/js/7664.5db3e36a.chunk.js.map",
"9080.f4810646.chunk.js.map": "./static/js/9080.f4810646.chunk.js.map",
"5961.494f0900.chunk.js.map": "./static/js/5961.494f0900.chunk.js.map",
"428.b5539f2b.chunk.js.map": "./static/js/428.b5539f2b.chunk.js.map",
"1069.6dfe4ad0.chunk.js.map": "./static/js/1069.6dfe4ad0.chunk.js.map",
"1140.0208375a.chunk.js.map": "./static/js/1140.0208375a.chunk.js.map",
"2094.4854485e.chunk.js.map": "./static/js/2094.4854485e.chunk.js.map",
"7950.455e5528.chunk.js.map": "./static/js/7950.455e5528.chunk.js.map",
"671.18a32329.chunk.js.map": "./static/js/671.18a32329.chunk.js.map",
"8961.8aa802d6.chunk.js.map": "./static/js/8961.8aa802d6.chunk.js.map",
"3967.ef703755.chunk.js.map": "./static/js/3967.ef703755.chunk.js.map",
"5677.ce92c9f5.chunk.css.map": "./static/css/5677.ce92c9f5.chunk.css.map",
"5677.654039a6.chunk.js.map": "./static/js/5677.654039a6.chunk.js.map",
"4360.ce92c9f5.chunk.css.map": "./static/css/4360.ce92c9f5.chunk.css.map",
"4360.bbbd8faf.chunk.js.map": "./static/js/4360.bbbd8faf.chunk.js.map",
"4109.04a89c2d.chunk.js.map": "./static/js/4109.04a89c2d.chunk.js.map",
"9080.bb419916.chunk.js.map": "./static/js/9080.bb419916.chunk.js.map",
"5961.04fd21a3.chunk.js.map": "./static/js/5961.04fd21a3.chunk.js.map",
"6549.e0686e32.chunk.js.map": "./static/js/6549.e0686e32.chunk.js.map",
"7498.5bdce2df.chunk.js.map": "./static/js/7498.5bdce2df.chunk.js.map",
"9421.fcfffa57.chunk.js.map": "./static/js/9421.fcfffa57.chunk.js.map",
"5284.e972df04.chunk.js.map": "./static/js/5284.e972df04.chunk.js.map",
"4818.d857d014.chunk.js.map": "./static/js/4818.d857d014.chunk.js.map",
"2401.dab77e89.chunk.js.map": "./static/js/2401.dab77e89.chunk.js.map",
"8724.8656472c.chunk.css.map": "./static/css/8724.8656472c.chunk.css.map",
"8724.8dc525b3.chunk.js.map": "./static/js/8724.8dc525b3.chunk.js.map",
"2182.d27fb965.chunk.js.map": "./static/js/2182.d27fb965.chunk.js.map",
"7764.9f2f6b88.chunk.js.map": "./static/js/7764.9f2f6b88.chunk.js.map",
"4220.7760227f.chunk.js.map": "./static/js/4220.7760227f.chunk.js.map",
"1719.54b40a33.chunk.js.map": "./static/js/1719.54b40a33.chunk.js.map",
"3320.646cbb74.chunk.js.map": "./static/js/3320.646cbb74.chunk.js.map",
"9923.634b0622.chunk.js.map": "./static/js/9923.634b0622.chunk.js.map",
"7498.07dd467b.chunk.js.map": "./static/js/7498.07dd467b.chunk.js.map",
"9421.a88c8e02.chunk.js.map": "./static/js/9421.a88c8e02.chunk.js.map",
"5284.e23070a0.chunk.js.map": "./static/js/5284.e23070a0.chunk.js.map",
"4818.65ba088c.chunk.js.map": "./static/js/4818.65ba088c.chunk.js.map",
"2401.74634660.chunk.js.map": "./static/js/2401.74634660.chunk.js.map",
"8724.ce92c9f5.chunk.css.map": "./static/css/8724.ce92c9f5.chunk.css.map",
"8724.cf8fb340.chunk.js.map": "./static/js/8724.cf8fb340.chunk.js.map",
"2182.468a5b4e.chunk.js.map": "./static/js/2182.468a5b4e.chunk.js.map",
"7764.3b798da3.chunk.js.map": "./static/js/7764.3b798da3.chunk.js.map",
"4220.308c5fb3.chunk.js.map": "./static/js/4220.308c5fb3.chunk.js.map",
"1719.fbaa408a.chunk.js.map": "./static/js/1719.fbaa408a.chunk.js.map",
"3320.2e853989.chunk.js.map": "./static/js/3320.2e853989.chunk.js.map",
"9923.25fa1a75.chunk.js.map": "./static/js/9923.25fa1a75.chunk.js.map",
"9586.c44d8f2b.chunk.js.map": "./static/js/9586.c44d8f2b.chunk.js.map",
"7261.39f723d9.chunk.js.map": "./static/js/7261.39f723d9.chunk.js.map",
"6436.c3cf82d9.chunk.js.map": "./static/js/6436.c3cf82d9.chunk.js.map",
"8343.c498d4af.chunk.js.map": "./static/js/8343.c498d4af.chunk.js.map",
"2841.73580ff7.chunk.js.map": "./static/js/2841.73580ff7.chunk.js.map",
"6167.2cf4273f.chunk.js.map": "./static/js/6167.2cf4273f.chunk.js.map",
"3698.2ea7b498.chunk.js.map": "./static/js/3698.2ea7b498.chunk.js.map",
"1971.52779ae6.chunk.js.map": "./static/js/1971.52779ae6.chunk.js.map",
"7346.5ff362f5.chunk.js.map": "./static/js/7346.5ff362f5.chunk.js.map",
"5144.5d8ad21e.chunk.js.map": "./static/js/5144.5d8ad21e.chunk.js.map",
"5125.2efed100.chunk.js.map": "./static/js/5125.2efed100.chunk.js.map",
"528.c588d7c6.chunk.js.map": "./static/js/528.c588d7c6.chunk.js.map",
"7187.7c4714ee.chunk.js.map": "./static/js/7187.7c4714ee.chunk.js.map",
"6173.3a1318da.chunk.js.map": "./static/js/6173.3a1318da.chunk.js.map",
"7146.77e9b1cc.chunk.js.map": "./static/js/7146.77e9b1cc.chunk.js.map",
"9924.8e40abd7.chunk.js.map": "./static/js/9924.8e40abd7.chunk.js.map",
"9193.6764f72b.chunk.js.map": "./static/js/9193.6764f72b.chunk.js.map",
"7451.0fa5a7e5.chunk.js.map": "./static/js/7451.0fa5a7e5.chunk.js.map",
"6511.9ad8d9a8.chunk.js.map": "./static/js/6511.9ad8d9a8.chunk.js.map",
"165.8656472c.chunk.css.map": "./static/css/165.8656472c.chunk.css.map",
"165.03f5d4c1.chunk.js.map": "./static/js/165.03f5d4c1.chunk.js.map",
"4121.b9b399ef.chunk.js.map": "./static/js/4121.b9b399ef.chunk.js.map",
"609.3f81f01c.chunk.js.map": "./static/js/609.3f81f01c.chunk.js.map",
"2892.1d404509.chunk.js.map": "./static/js/2892.1d404509.chunk.js.map",
"7926.7b87452c.chunk.js.map": "./static/js/7926.7b87452c.chunk.js.map",
"6145.1b8916cd.chunk.js.map": "./static/js/6145.1b8916cd.chunk.js.map",
"6951.8656472c.chunk.css.map": "./static/css/6951.8656472c.chunk.css.map",
"6951.34c85fa1.chunk.js.map": "./static/js/6951.34c85fa1.chunk.js.map",
"2966.3df2cc1f.chunk.js.map": "./static/js/2966.3df2cc1f.chunk.js.map",
"4177.b1d4c761.chunk.js.map": "./static/js/4177.b1d4c761.chunk.js.map",
"9679.3942615d.chunk.js.map": "./static/js/9679.3942615d.chunk.js.map",
"8333.a7249061.chunk.js.map": "./static/js/8333.a7249061.chunk.js.map",
"1711.2e4f085e.chunk.js.map": "./static/js/1711.2e4f085e.chunk.js.map",
"9.40a0afdf.chunk.js.map": "./static/js/9.40a0afdf.chunk.js.map",
"4487.625e74d7.chunk.js.map": "./static/js/4487.625e74d7.chunk.js.map",
"6866.eca0c7d9.chunk.js.map": "./static/js/6866.eca0c7d9.chunk.js.map",
"8564.289a240e.chunk.js.map": "./static/js/8564.289a240e.chunk.js.map",
"3152.4d22e959.chunk.js.map": "./static/js/3152.4d22e959.chunk.js.map",
"7007.7afa6cca.chunk.js.map": "./static/js/7007.7afa6cca.chunk.js.map",
"14.ec5eac10.chunk.js.map": "./static/js/14.ec5eac10.chunk.js.map",
"892.c5842324.chunk.js.map": "./static/js/892.c5842324.chunk.js.map",
"2066.81f72337.chunk.js.map": "./static/js/2066.81f72337.chunk.js.map",
"7261.98082dab.chunk.js.map": "./static/js/7261.98082dab.chunk.js.map",
"6436.f2bab163.chunk.js.map": "./static/js/6436.f2bab163.chunk.js.map",
"8343.b174034e.chunk.js.map": "./static/js/8343.b174034e.chunk.js.map",
"2841.95af1213.chunk.js.map": "./static/js/2841.95af1213.chunk.js.map",
"6167.edad2368.chunk.js.map": "./static/js/6167.edad2368.chunk.js.map",
"3698.49eb7411.chunk.js.map": "./static/js/3698.49eb7411.chunk.js.map",
"1971.306de04b.chunk.js.map": "./static/js/1971.306de04b.chunk.js.map",
"7346.8e455eaa.chunk.js.map": "./static/js/7346.8e455eaa.chunk.js.map",
"5144.f66d75af.chunk.js.map": "./static/js/5144.f66d75af.chunk.js.map",
"5125.9822771e.chunk.js.map": "./static/js/5125.9822771e.chunk.js.map",
"1973.55987b98.chunk.js.map": "./static/js/1973.55987b98.chunk.js.map",
"7187.b3ba71a1.chunk.js.map": "./static/js/7187.b3ba71a1.chunk.js.map",
"6173.b70c4e1a.chunk.js.map": "./static/js/6173.b70c4e1a.chunk.js.map",
"7146.09b3a43c.chunk.js.map": "./static/js/7146.09b3a43c.chunk.js.map",
"9924.a7c06331.chunk.js.map": "./static/js/9924.a7c06331.chunk.js.map",
"9193.cbd8d842.chunk.js.map": "./static/js/9193.cbd8d842.chunk.js.map",
"7451.43cf8bf6.chunk.js.map": "./static/js/7451.43cf8bf6.chunk.js.map",
"165.ce92c9f5.chunk.css.map": "./static/css/165.ce92c9f5.chunk.css.map",
"165.6ba70895.chunk.js.map": "./static/js/165.6ba70895.chunk.js.map",
"4121.17b5e70f.chunk.js.map": "./static/js/4121.17b5e70f.chunk.js.map",
"609.218c57b6.chunk.js.map": "./static/js/609.218c57b6.chunk.js.map",
"3421.aa3eeb04.chunk.js.map": "./static/js/3421.aa3eeb04.chunk.js.map",
"2892.f2ab7fe7.chunk.js.map": "./static/js/2892.f2ab7fe7.chunk.js.map",
"7926.d155ef62.chunk.js.map": "./static/js/7926.d155ef62.chunk.js.map",
"6145.819d854c.chunk.js.map": "./static/js/6145.819d854c.chunk.js.map",
"6951.ce92c9f5.chunk.css.map": "./static/css/6951.ce92c9f5.chunk.css.map",
"6951.e58557fd.chunk.js.map": "./static/js/6951.e58557fd.chunk.js.map",
"2966.c61fbf10.chunk.js.map": "./static/js/2966.c61fbf10.chunk.js.map",
"4177.bfe448c8.chunk.js.map": "./static/js/4177.bfe448c8.chunk.js.map",
"9679.2b8424ba.chunk.js.map": "./static/js/9679.2b8424ba.chunk.js.map",
"8333.b0c9e377.chunk.js.map": "./static/js/8333.b0c9e377.chunk.js.map",
"1711.a046e26c.chunk.js.map": "./static/js/1711.a046e26c.chunk.js.map",
"9.0e073695.chunk.js.map": "./static/js/9.0e073695.chunk.js.map",
"4487.3ad24a2d.chunk.js.map": "./static/js/4487.3ad24a2d.chunk.js.map",
"6866.aa2f03f0.chunk.js.map": "./static/js/6866.aa2f03f0.chunk.js.map",
"8564.11a3023b.chunk.js.map": "./static/js/8564.11a3023b.chunk.js.map",
"3152.daa99377.chunk.js.map": "./static/js/3152.daa99377.chunk.js.map",
"7007.3606231e.chunk.js.map": "./static/js/7007.3606231e.chunk.js.map",
"14.25ecb28d.chunk.js.map": "./static/js/14.25ecb28d.chunk.js.map",
"2066.5180dac0.chunk.js.map": "./static/js/2066.5180dac0.chunk.js.map",
"5444.e8727da9.chunk.js.map": "./static/js/5444.e8727da9.chunk.js.map",
"5594.6e7b403a.chunk.js.map": "./static/js/5594.6e7b403a.chunk.js.map",
"3990.5e998248.chunk.js.map": "./static/js/3990.5e998248.chunk.js.map",
"5399.5699b879.chunk.js.map": "./static/js/5399.5699b879.chunk.js.map",
"892.2aaf58f1.chunk.js.map": "./static/js/892.2aaf58f1.chunk.js.map",
"3690.536fb187.chunk.js.map": "./static/js/3690.536fb187.chunk.js.map",
"5399.6bc650ac.chunk.js.map": "./static/js/5399.6bc650ac.chunk.js.map",
"606.28fdb5bc.chunk.js.map": "./static/js/606.28fdb5bc.chunk.js.map",
"9998.e7c54804.chunk.js.map": "./static/js/9998.e7c54804.chunk.js.map",
"9769.733c21d3.chunk.js.map": "./static/js/9769.733c21d3.chunk.js.map",
"9998.1a613ce5.chunk.js.map": "./static/js/9998.1a613ce5.chunk.js.map",
"9769.a2bfb4d7.chunk.js.map": "./static/js/9769.a2bfb4d7.chunk.js.map",
"8954.00599ff1.chunk.js.map": "./static/js/8954.00599ff1.chunk.js.map",
"7248.1e0c4e19.chunk.js.map": "./static/js/7248.1e0c4e19.chunk.js.map",
"4837.afb48283.chunk.js.map": "./static/js/4837.afb48283.chunk.js.map",
"4837.98af4cfe.chunk.js.map": "./static/js/4837.98af4cfe.chunk.js.map",
"3020.3b2e782c.chunk.js.map": "./static/js/3020.3b2e782c.chunk.js.map",
"9056.ad0432f8.chunk.js.map": "./static/js/9056.ad0432f8.chunk.js.map",
"9056.41f0e489.chunk.js.map": "./static/js/9056.41f0e489.chunk.js.map",
"3360.453f0701.chunk.js.map": "./static/js/3360.453f0701.chunk.js.map",
"7561.81edce87.chunk.js.map": "./static/js/7561.81edce87.chunk.js.map",
"8420.68d5dfb4.chunk.js.map": "./static/js/8420.68d5dfb4.chunk.js.map",
"1420.517f0e88.chunk.js.map": "./static/js/1420.517f0e88.chunk.js.map",
"2309.49be65c5.chunk.js.map": "./static/js/2309.49be65c5.chunk.js.map",
"2364.a32e93a4.chunk.js.map": "./static/js/2364.a32e93a4.chunk.js.map",
"3356.3534f8c0.chunk.js.map": "./static/js/3356.3534f8c0.chunk.js.map",
"1233.9903fc82.chunk.js.map": "./static/js/1233.9903fc82.chunk.js.map",
"3356.05b42758.chunk.js.map": "./static/js/3356.05b42758.chunk.js.map",
"8016.b0ab3b67.chunk.js.map": "./static/js/8016.b0ab3b67.chunk.js.map",
"6851.3b47e1b9.chunk.js.map": "./static/js/6851.3b47e1b9.chunk.js.map",
"2253.0ed59cf3.chunk.js.map": "./static/js/2253.0ed59cf3.chunk.js.map",
"9415.8ad63e79.chunk.js.map": "./static/js/9415.8ad63e79.chunk.js.map",
"1811.cf61844d.chunk.js.map": "./static/js/1811.cf61844d.chunk.js.map",
"7857.cc3da495.chunk.js.map": "./static/js/7857.cc3da495.chunk.js.map",
"3538.1b5c3c94.chunk.js.map": "./static/js/3538.1b5c3c94.chunk.js.map",
"3290.cf0a5a06.chunk.js.map": "./static/js/3290.cf0a5a06.chunk.js.map",
"7348.44810a11.chunk.js.map": "./static/js/7348.44810a11.chunk.js.map",
"2253.d527f443.chunk.js.map": "./static/js/2253.d527f443.chunk.js.map",
"6427.028089e0.chunk.js.map": "./static/js/6427.028089e0.chunk.js.map",
"3538.60e98cef.chunk.js.map": "./static/js/3538.60e98cef.chunk.js.map",
"8749.fa0fb710.chunk.js.map": "./static/js/8749.fa0fb710.chunk.js.map",
"7348.b037b952.chunk.js.map": "./static/js/7348.b037b952.chunk.js.map",
"1760.8589ea77.chunk.js.map": "./static/js/1760.8589ea77.chunk.js.map",
"8708.1735e6e6.chunk.js.map": "./static/js/8708.1735e6e6.chunk.js.map",
"3045.691c66fa.chunk.js.map": "./static/js/3045.691c66fa.chunk.js.map",
"7049.620c3eec.chunk.js.map": "./static/js/7049.620c3eec.chunk.js.map",
"2873.ebf17fc8.chunk.js.map": "./static/js/2873.ebf17fc8.chunk.js.map",
"8708.70b62dd5.chunk.js.map": "./static/js/8708.70b62dd5.chunk.js.map",
"2594.77f64a1f.chunk.js.map": "./static/js/2594.77f64a1f.chunk.js.map",
"440.5bb0a106.chunk.js.map": "./static/js/440.5bb0a106.chunk.js.map",
"3045.f1cc8d42.chunk.js.map": "./static/js/3045.f1cc8d42.chunk.js.map",
"4817.2c511e72.chunk.js.map": "./static/js/4817.2c511e72.chunk.js.map",
"227.5ba677a7.chunk.js.map": "./static/js/227.5ba677a7.chunk.js.map",
"4105.1b1d6fe0.chunk.js.map": "./static/js/4105.1b1d6fe0.chunk.js.map",
"8174.540bd07a.chunk.js.map": "./static/js/8174.540bd07a.chunk.js.map",
"7042.6737005f.chunk.js.map": "./static/js/7042.6737005f.chunk.js.map",
"3201.8afb9fa9.chunk.js.map": "./static/js/3201.8afb9fa9.chunk.js.map",
"1724.30bcf6f8.chunk.js.map": "./static/js/1724.30bcf6f8.chunk.js.map",
"7454.eb7fb0a0.chunk.js.map": "./static/js/7454.eb7fb0a0.chunk.js.map",
"6873.177d160e.chunk.js.map": "./static/js/6873.177d160e.chunk.js.map",
"3453.3c290198.chunk.js.map": "./static/js/3453.3c290198.chunk.js.map"
"938.8f6a5aa1.chunk.js.map": "./static/js/938.8f6a5aa1.chunk.js.map",
"4105.a498ca05.chunk.js.map": "./static/js/4105.a498ca05.chunk.js.map",
"9876.3e5a4f83.chunk.js.map": "./static/js/9876.3e5a4f83.chunk.js.map",
"7777.7ea62662.chunk.js.map": "./static/js/7777.7ea62662.chunk.js.map",
"7042.343b48b2.chunk.js.map": "./static/js/7042.343b48b2.chunk.js.map",
"3201.9f8a570f.chunk.js.map": "./static/js/3201.9f8a570f.chunk.js.map",
"7664.9ba57569.chunk.js.map": "./static/js/7664.9ba57569.chunk.js.map",
"5170.2d3119fa.chunk.js.map": "./static/js/5170.2d3119fa.chunk.js.map",
"7454.6a33b546.chunk.js.map": "./static/js/7454.6a33b546.chunk.js.map",
"6873.a4c0dde3.chunk.js.map": "./static/js/6873.a4c0dde3.chunk.js.map",
"3453.9ef32892.chunk.js.map": "./static/js/3453.9ef32892.chunk.js.map"
},
"entrypoints": [
"static/css/main.c4c1effe.css",
"static/js/main.e413bbf1.js"
"static/js/main.c7ee1a1d.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.e413bbf1.js"></script><link href="./static/css/main.c4c1effe.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" fill="none" r="20.2" stroke-width="3.6"></circle></svg></div></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.c7ee1a1d.js"></script><link href="./static/css/main.c4c1effe.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" fill="none" r="20.2" stroke-width="3.6"></circle></svg></div></div></body></html>

View File

@@ -1,2 +1,2 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=165.8656472c.chunk.css.map*/
/*# sourceMappingURL=165.ce92c9f5.chunk.css.map*/

View File

@@ -1 +1 @@
{"version":3,"file":"static/css/165.8656472c.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}
{"version":3,"file":"static/css/165.ce92c9f5.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -1,2 +1,2 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=4360.8656472c.chunk.css.map*/
/*# sourceMappingURL=4360.ce92c9f5.chunk.css.map*/

View File

@@ -1 +1 @@
{"version":3,"file":"static/css/4360.8656472c.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}
{"version":3,"file":"static/css/4360.ce92c9f5.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -1,2 +1,2 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=5677.8656472c.chunk.css.map*/
/*# sourceMappingURL=5673.ce92c9f5.chunk.css.map*/

View File

@@ -1 +1 @@
{"version":3,"file":"static/css/5677.8656472c.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}
{"version":3,"file":"static/css/5673.ce92c9f5.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -1,2 +1,2 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=6696.8656472c.chunk.css.map*/
/*# sourceMappingURL=5677.ce92c9f5.chunk.css.map*/

View File

@@ -1 +1 @@
{"version":3,"file":"static/css/6696.8656472c.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}
{"version":3,"file":"static/css/5677.ce92c9f5.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -1,2 +0,0 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=6951.8656472c.chunk.css.map*/

View File

@@ -0,0 +1,2 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=6951.ce92c9f5.chunk.css.map*/

View File

@@ -1 +1 @@
{"version":3,"file":"static/css/6951.8656472c.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}
{"version":3,"file":"static/css/6951.ce92c9f5.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -1,2 +0,0 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=8724.8656472c.chunk.css.map*/

View File

@@ -1 +0,0 @@
{"version":3,"file":"static/css/8724.8656472c.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -0,0 +1,2 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=8724.ce92c9f5.chunk.css.map*/

View File

@@ -0,0 +1 @@
{"version":3,"file":"static/css/8724.ce92c9f5.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -1,2 +0,0 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=9807.8656472c.chunk.css.map*/

View File

@@ -1 +0,0 @@
{"version":3,"file":"static/css/9807.8656472c.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

View File

@@ -0,0 +1,2 @@
.cm-s-dracula.CodeMirror,.cm-s-dracula .CodeMirror-gutters{background-color:#282a36!important;border:none;color:#f8f8f2!important}.cm-s-dracula .CodeMirror-gutters{color:#282a36}.cm-s-dracula .CodeMirror-cursor{border-left:thin solid #f8f8f0}.cm-s-dracula .CodeMirror-linenumber{color:#6d8a88}.cm-s-dracula .CodeMirror-selected{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::selection,.cm-s-dracula .CodeMirror-line>span::selection,.cm-s-dracula .CodeMirror-line>span>span::selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-line::-moz-selection,.cm-s-dracula .CodeMirror-line>span::-moz-selection,.cm-s-dracula .CodeMirror-line>span>span::-moz-selection{background:hsla(0,0%,100%,.1)}.cm-s-dracula span.cm-comment{color:#6272a4}.cm-s-dracula span.cm-string,.cm-s-dracula span.cm-string-2{color:#f1fa8c}.cm-s-dracula span.cm-number{color:#bd93f9}.cm-s-dracula span.cm-variable{color:#50fa7b}.cm-s-dracula span.cm-variable-2{color:#fff}.cm-s-dracula span.cm-def{color:#50fa7b}.cm-s-dracula span.cm-keyword,.cm-s-dracula span.cm-operator{color:#ff79c6}.cm-s-dracula span.cm-atom{color:#bd93f9}.cm-s-dracula span.cm-meta{color:#f8f8f2}.cm-s-dracula span.cm-tag{color:#ff79c6}.cm-s-dracula span.cm-attribute,.cm-s-dracula span.cm-qualifier{color:#50fa7b}.cm-s-dracula span.cm-property{color:#66d9ef}.cm-s-dracula span.cm-builtin{color:#50fa7b}.cm-s-dracula span.cm-type,.cm-s-dracula span.cm-variable-3{color:#ffb86c}.cm-s-dracula .CodeMirror-activeline-background{background:hsla(0,0%,100%,.1)}.cm-s-dracula .CodeMirror-matchingbracket{color:#fff!important;text-decoration:underline}
/*# sourceMappingURL=9807.ce92c9f5.chunk.css.map*/

View File

@@ -0,0 +1 @@
{"version":3,"file":"static/css/9807.ce92c9f5.chunk.css","mappings":"AAUA,2DACE,kCAAoC,CAEpC,WAAY,CADZ,uBAEF,CACA,kCAAoC,aAAgB,CACpD,iCAAmC,8BAAiC,CACpE,qCAAuC,aAAgB,CACvD,mCAAqC,6BAAuC,CAC5E,6IAAuJ,6BAAuC,CAC9L,4JAAsK,6BAAuC,CAC7M,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAC/E,6BAA+B,aAAgB,CAC/C,+BAAiC,aAAgB,CACjD,iCAAmC,UAAc,CACjD,0BAA4B,aAAgB,CAE5C,6DAAgC,aAAgB,CAChD,2BAA6B,aAAgB,CAC7C,2BAA6B,aAAgB,CAC7C,0BAA4B,aAAgB,CAE5C,gEAAkC,aAAgB,CAClD,+BAAiC,aAAgB,CACjD,8BAAgC,aAAgB,CAChD,4DAA+D,aAAgB,CAE/E,gDAAkD,6BAAmC,CACrF,0CAAwE,oBAAuB,CAAnD,yBAAqD","sources":["../node_modules/codemirror/theme/dracula.css"],"sourcesContent":["/*\n\n Name: dracula\n Author: Michael Kaminsky (http://github.com/mkaminsky11)\n\n Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme)\n\n*/\n\n\n.cm-s-dracula.CodeMirror, .cm-s-dracula .CodeMirror-gutters {\n background-color: #282a36 !important;\n color: #f8f8f2 !important;\n border: none;\n}\n.cm-s-dracula .CodeMirror-gutters { color: #282a36; }\n.cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; }\n.cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; }\n.cm-s-dracula .CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }\n.cm-s-dracula span.cm-comment { color: #6272a4; }\n.cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; }\n.cm-s-dracula span.cm-number { color: #bd93f9; }\n.cm-s-dracula span.cm-variable { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-2 { color: white; }\n.cm-s-dracula span.cm-def { color: #50fa7b; }\n.cm-s-dracula span.cm-operator { color: #ff79c6; }\n.cm-s-dracula span.cm-keyword { color: #ff79c6; }\n.cm-s-dracula span.cm-atom { color: #bd93f9; }\n.cm-s-dracula span.cm-meta { color: #f8f8f2; }\n.cm-s-dracula span.cm-tag { color: #ff79c6; }\n.cm-s-dracula span.cm-attribute { color: #50fa7b; }\n.cm-s-dracula span.cm-qualifier { color: #50fa7b; }\n.cm-s-dracula span.cm-property { color: #66d9ef; }\n.cm-s-dracula span.cm-builtin { color: #50fa7b; }\n.cm-s-dracula span.cm-variable-3, .cm-s-dracula span.cm-type { color: #ffb86c; }\n\n.cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); }\n.cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }\n"],"names":[],"sourceRoot":""}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More