Merge branch 'master' into sevey/composite-actions
This commit is contained in:
commit
ac15135099
|
@ -6,48 +6,37 @@ name: Nginx Lua Unit Tests
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- ".github/workflows/nginx-lua-unit-tests.yml"
|
||||
- "docker/nginx/libs/**.lua"
|
||||
- master
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/nginx-lua-unit-tests.yml"
|
||||
- "docker/nginx/libs/**.lua"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container: openresty/openresty:1.19.9.1-focal
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.x"
|
||||
architecture: "x64"
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
pip install hererocks
|
||||
hererocks env --lua=5.1 -rlatest
|
||||
source env/bin/activate
|
||||
luarocks install lua-resty-http
|
||||
luarocks install hasher
|
||||
luarocks install busted
|
||||
luarocks install luacov
|
||||
luarocks install hasher
|
||||
luarocks install luacheck
|
||||
|
||||
- name: Lint code
|
||||
run: |
|
||||
source env/bin/activate
|
||||
luacheck docker/nginx/libs --std ngx_lua+busted
|
||||
- name: Lint Code With Luacheck
|
||||
run: luacheck docker/nginx/libs --std ngx_lua+busted
|
||||
|
||||
- name: Unit Tests
|
||||
run: |
|
||||
source env/bin/activate
|
||||
busted --verbose --coverage --pattern=spec --directory=docker/nginx/libs .
|
||||
cd docker/nginx/libs && luacov
|
||||
- name: Run Tests With Busted
|
||||
# ran from root repo directory; produces luacov.stats.out file
|
||||
run: docker/nginx/testing/rbusted --lpath='docker/nginx/libs/?.lua;docker/nginx/libs/?/?.lua' --verbose --coverage --pattern=spec docker/nginx/libs
|
||||
|
||||
- uses: codecov/codecov-action@v2
|
||||
- name: Generate Code Coverage Report With Luacov
|
||||
# requires config file in cwd; produces luacov.report.out file
|
||||
run: cp docker/nginx/testing/.luacov . && luacov && rm .luacov
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
||||
with:
|
||||
directory: docker/nginx/libs
|
||||
root_dir: ${GITHUB_WORKSPACE}
|
||||
files: ./luacov.report.out
|
||||
flags: nginx-lua
|
||||
|
|
|
@ -86,6 +86,10 @@ __pycache__
|
|||
/.idea/
|
||||
/venv*
|
||||
|
||||
# Luacov file
|
||||
luacov.stats.out
|
||||
luacov.report.out
|
||||
|
||||
# Setup-script log files
|
||||
setup-scripts/serverload.log
|
||||
setup-scripts/serverload.json
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
Latest Skynet Webportal setup documentation and the setup process Skynet Labs
|
||||
supports is located at https://docs.siasky.net/webportal-management/overview.
|
||||
|
||||
Some of the scripts and setup documentation contained in this repository
|
||||
(`skynet-webportal`) can be outdated and generally should not be used.
|
||||
Some scripts and setup documentation contained in this repository
|
||||
(`skynet-webportal`) may be outdated and generally should not be used.
|
||||
|
||||
## Web application
|
||||
|
||||
|
@ -35,7 +35,7 @@ For the purposes of complying with our code license, you can use the following S
|
|||
`fb6c9320bc7e01fbb9cd8d8c3caaa371386928793c736837832e634aaaa484650a3177d6714a`
|
||||
|
||||
## Running a Portal
|
||||
For those interested in running a Webportal, head over to our developer docs [here](https://docs.siasky.net/webportal-management/overview.) to learn more.
|
||||
For those interested in running a Webportal, head over to our developer docs [here](https://portal-docs.skynetlabs.com/) to learn more.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@ services:
|
|||
- ./docker/data/dashboard-v2/public:/usr/app/public
|
||||
networks:
|
||||
shared:
|
||||
ipv4_address: 10.10.10.90
|
||||
ipv4_address: 10.10.10.86
|
||||
expose:
|
||||
- 9000
|
||||
depends_on:
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
include /etc/nginx/conf.d/include/cors;
|
||||
include /etc/nginx/conf.d/include/track-download;
|
||||
|
||||
limit_conn downloads_by_ip 100; # ddos protection: max 100 downloads at a time
|
||||
|
||||
|
@ -37,3 +36,18 @@ proxy_read_timeout 600;
|
|||
proxy_set_header User-Agent: Sia-Agent;
|
||||
|
||||
proxy_pass http://sia:9980/skynet/skylink/$skylink$path$is_args$args;
|
||||
|
||||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
local skynet_modules = require("skynet.modules")
|
||||
local skynet_scanner = require("skynet.scanner")
|
||||
local skynet_tracker = require("skynet.tracker")
|
||||
|
||||
if skynet_modules.is_enabled("a") then
|
||||
skynet_tracker.track_download(ngx.header["Skynet-Skylink"], ngx.status, skynet_account.get_auth_headers(), ngx.var.body_bytes_sent)
|
||||
end
|
||||
|
||||
if skynet_modules.is_enabled("s") then
|
||||
skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"])
|
||||
end
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
include /etc/nginx/conf.d/include/cors;
|
||||
include /etc/nginx/conf.d/include/sia-auth;
|
||||
include /etc/nginx/conf.d/include/track-registry;
|
||||
|
||||
limit_req zone=registry_access_by_ip burst=600 nodelay;
|
||||
limit_req zone=registry_access_by_ip_throttled burst=200 nodelay;
|
||||
|
@ -30,3 +29,10 @@ access_by_lua_block {
|
|||
end
|
||||
end
|
||||
}
|
||||
|
||||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
local skynet_tracker = require("skynet.tracker")
|
||||
|
||||
skynet_tracker.track_registry(ngx.status, skynet_account.get_auth_headers(), ngx.req.get_method())
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
|
||||
-- tracking runs only when request comes from authenticated user
|
||||
if skynet_account.is_authenticated() then
|
||||
local function track(premature, skylink, status, body_bytes_sent, auth_headers)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
local query = table.concat({ "status=" .. status, "bytes=" .. body_bytes_sent }, "&")
|
||||
|
||||
-- 10.10.10.70 points to accounts service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/track/download/" .. skylink .. "?" .. query, {
|
||||
method = "POST",
|
||||
headers = auth_headers,
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= ngx.HTTP_NO_CONTENT) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed accounts service request /track/download/" .. skylink .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
if ngx.header["Skynet-Skylink"] and ngx.status >= ngx.HTTP_OK and ngx.status < ngx.HTTP_SPECIAL_RESPONSE then
|
||||
local auth_headers = skynet_account.get_auth_headers()
|
||||
local ok, err = ngx.timer.at(0, track, ngx.header["Skynet-Skylink"], ngx.status, ngx.var.body_bytes_sent, auth_headers)
|
||||
if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
|
||||
-- this block runs only when scanner module is enabled
|
||||
if os.getenv("PORTAL_MODULES"):match("s") then
|
||||
local function scan(premature, skylink)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
|
||||
-- 10.10.10.101 points to malware-scanner service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.101:4000/scan/" .. skylink, {
|
||||
method = "POST",
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= ngx.HTTP_OK) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed malware-scanner request /scan/" .. skylink .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
-- scan all skylinks but make sure to only run if skylink is present (empty if request failed)
|
||||
if ngx.header["Skynet-Skylink"] then
|
||||
local ok, err = ngx.timer.at(0, scan, ngx.header["Skynet-Skylink"])
|
||||
if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
|
||||
-- tracking runs only when request comes from authenticated user
|
||||
if skynet_account.is_authenticated() then
|
||||
local function track(premature, request_method, auth_headers)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
|
||||
-- based on request method we assign a registry action string used
|
||||
-- in track endpoint namely "read" for GET and "write" for POST
|
||||
local registry_action = request_method == "GET" and "read" or "write"
|
||||
|
||||
-- 10.10.10.70 points to accounts service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/track/registry/" .. registry_action, {
|
||||
method = "POST",
|
||||
headers = auth_headers,
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= ngx.HTTP_NO_CONTENT) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed accounts service request /track/registry/" .. registry_action .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
if ngx.status == ngx.HTTP_OK or ngx.status == ngx.HTTP_NOT_FOUND then
|
||||
local auth_headers = skynet_account.get_auth_headers()
|
||||
local ok, err = ngx.timer.at(0, track, ngx.req.get_method(), auth_headers)
|
||||
if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
|
||||
-- tracking runs only when request comes from authenticated user
|
||||
if skynet_account.is_authenticated() then
|
||||
local function track(premature, skylink, auth_headers)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
|
||||
-- 10.10.10.70 points to accounts service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/track/upload/" .. skylink, {
|
||||
method = "POST",
|
||||
headers = auth_headers,
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= ngx.HTTP_NO_CONTENT) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed accounts service request /track/upload/" .. skylink .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
-- report all skylinks (header empty if request failed) but only if jwt is preset (user is authenticated)
|
||||
if ngx.header["Skynet-Skylink"] then
|
||||
local auth_headers = skynet_account.get_auth_headers()
|
||||
local ok, err = ngx.timer.at(0, track, ngx.header["Skynet-Skylink"], auth_headers)
|
||||
if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
|
||||
-- this block runs only when scanner module is enabled
|
||||
if os.getenv("PORTAL_MODULES"):match("s") then
|
||||
local function scan(premature, skylink)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
|
||||
-- 10.10.10.101 points to malware-scanner service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.101:4000/scan/" .. skylink, {
|
||||
method = "POST",
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= ngx.HTTP_OK) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed malware-scanner request /scan/" .. skylink .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
-- scan all skylinks but make sure to only run if skylink is present (empty if request failed)
|
||||
if ngx.header["Skynet-Skylink"] then
|
||||
local ok, err = ngx.timer.at(0, scan, ngx.header["Skynet-Skylink"])
|
||||
if err then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
}
|
|
@ -206,7 +206,6 @@ location /skynet/registry/subscription {
|
|||
location /skynet/skyfile {
|
||||
include /etc/nginx/conf.d/include/cors;
|
||||
include /etc/nginx/conf.d/include/sia-auth;
|
||||
include /etc/nginx/conf.d/include/track-upload;
|
||||
include /etc/nginx/conf.d/include/generate-siapath;
|
||||
include /etc/nginx/conf.d/include/portal-access-check;
|
||||
|
||||
|
@ -228,12 +227,26 @@ location /skynet/skyfile {
|
|||
|
||||
# proxy this call to siad endpoint (make sure the ip is correct)
|
||||
proxy_pass http://sia:9980/skynet/skyfile/$dir1/$dir2/$dir3$is_args$args;
|
||||
|
||||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
local skynet_modules = require("skynet.modules")
|
||||
local skynet_scanner = require("skynet.scanner")
|
||||
local skynet_tracker = require("skynet.tracker")
|
||||
|
||||
if skynet_modules.is_enabled("a") then
|
||||
skynet_tracker.track_upload(ngx.header["Skynet-Skylink"], ngx.status, skynet_account.get_auth_headers())
|
||||
end
|
||||
|
||||
if skynet_modules.is_enabled("s") then
|
||||
skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"])
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
# endpoint implementing resumable file uploads open protocol https://tus.io
|
||||
location /skynet/tus {
|
||||
include /etc/nginx/conf.d/include/cors-headers; # include cors headers but do not overwrite OPTIONS response
|
||||
include /etc/nginx/conf.d/include/track-upload;
|
||||
|
||||
limit_req zone=uploads_by_ip burst=10 nodelay;
|
||||
limit_req zone=uploads_by_ip_throttled;
|
||||
|
@ -294,12 +307,26 @@ location /skynet/tus {
|
|||
end
|
||||
end
|
||||
}
|
||||
|
||||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
local skynet_modules = require("skynet.modules")
|
||||
local skynet_scanner = require("skynet.scanner")
|
||||
local skynet_tracker = require("skynet.tracker")
|
||||
|
||||
if skynet_modules.is_enabled("a") then
|
||||
skynet_tracker.track_upload(ngx.header["Skynet-Skylink"], ngx.status, skynet_account.get_auth_headers())
|
||||
end
|
||||
|
||||
if skynet_modules.is_enabled("s") then
|
||||
skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"])
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
location /skynet/pin {
|
||||
include /etc/nginx/conf.d/include/cors;
|
||||
include /etc/nginx/conf.d/include/sia-auth;
|
||||
include /etc/nginx/conf.d/include/track-upload;
|
||||
include /etc/nginx/conf.d/include/generate-siapath;
|
||||
include /etc/nginx/conf.d/include/portal-access-check;
|
||||
|
||||
|
@ -311,6 +338,21 @@ location /skynet/pin {
|
|||
|
||||
proxy_set_header User-Agent: Sia-Agent;
|
||||
proxy_pass http://sia:9980$uri?siapath=$dir1/$dir2/$dir3&$args;
|
||||
|
||||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
local skynet_modules = require("skynet.modules")
|
||||
local skynet_scanner = require("skynet.scanner")
|
||||
local skynet_tracker = require("skynet.tracker")
|
||||
|
||||
if skynet_modules.is_enabled("a") then
|
||||
skynet_tracker.track_upload(ngx.header["Skynet-Skylink"], ngx.status, skynet_account.get_auth_headers())
|
||||
end
|
||||
|
||||
if skynet_modules.is_enabled("s") then
|
||||
skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"])
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
location /skynet/metadata {
|
||||
|
@ -357,7 +399,6 @@ location ~ "^/file/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" {
|
|||
|
||||
location /skynet/trustless/basesector {
|
||||
include /etc/nginx/conf.d/include/cors;
|
||||
include /etc/nginx/conf.d/include/track-download;
|
||||
|
||||
limit_conn downloads_by_ip 100; # ddos protection: max 100 downloads at a time
|
||||
|
||||
|
@ -391,6 +432,21 @@ location /skynet/trustless/basesector {
|
|||
|
||||
proxy_set_header User-Agent: Sia-Agent;
|
||||
proxy_pass http://sia:9980;
|
||||
|
||||
log_by_lua_block {
|
||||
local skynet_account = require("skynet.account")
|
||||
local skynet_modules = require("skynet.modules")
|
||||
local skynet_scanner = require("skynet.scanner")
|
||||
local skynet_tracker = require("skynet.tracker")
|
||||
|
||||
if skynet_modules.is_enabled("a") then
|
||||
skynet_tracker.track_download(ngx.header["Skynet-Skylink"], ngx.status, skynet_account.get_auth_headers(), ngx.var.body_bytes_sent)
|
||||
end
|
||||
|
||||
if skynet_modules.is_enabled("s") then
|
||||
skynet_scanner.scan_skylink(ngx.header["Skynet-Skylink"])
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
location /__internal/do/not/use/accounts {
|
||||
|
|
|
@ -5,6 +5,7 @@ location / {
|
|||
set $path $uri;
|
||||
|
||||
rewrite_by_lua_block {
|
||||
local cjson = require("cjson")
|
||||
local cache = ngx.shared.dnslink
|
||||
local cache_value = cache:get(ngx.var.host)
|
||||
|
||||
|
@ -28,13 +29,23 @@ location / {
|
|||
ngx.exit(ngx.status)
|
||||
end
|
||||
else
|
||||
ngx.var.skylink = res.body
|
||||
local resolved = cjson.decode(res.body)
|
||||
|
||||
ngx.var.skylink = resolved.skylink
|
||||
if resolved.sponsor then
|
||||
ngx.req.set_header("Skynet-Api-Key", resolved.sponsor)
|
||||
end
|
||||
|
||||
local cache_ttl = 300 -- 5 minutes cache expire time
|
||||
cache:set(ngx.var.host, ngx.var.skylink, cache_ttl)
|
||||
cache:set(ngx.var.host, res.body, cache_ttl)
|
||||
end
|
||||
else
|
||||
ngx.var.skylink = cache_value
|
||||
local resolved = cjson.decode(cache_value)
|
||||
|
||||
ngx.var.skylink = resolved.skylink
|
||||
if resolved.sponsor then
|
||||
ngx.req.set_header("Skynet-Api-Key", resolved.sponsor)
|
||||
end
|
||||
end
|
||||
|
||||
ngx.var.skylink = require("skynet.skylink").parse(ngx.var.skylink)
|
||||
|
|
|
@ -59,7 +59,9 @@ function _M.exit_access_forbidden(message)
|
|||
end
|
||||
|
||||
function _M.accounts_enabled()
|
||||
return os.getenv("PORTAL_MODULES"):match("a") ~= nil
|
||||
local skynet_modules = require("skynet.modules")
|
||||
|
||||
return skynet_modules.is_enabled("a")
|
||||
end
|
||||
|
||||
function _M.get_account_limits()
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
local _M = {}
|
||||
|
||||
local utils = require("utils")
|
||||
|
||||
function _M.is_enabled(module_abbr)
|
||||
if type(module_abbr) ~= "string" or module_abbr:len() ~= 1 then
|
||||
error("Module abbreviation '" .. tostring(module_abbr) .. "' should be exactly one character long string")
|
||||
end
|
||||
|
||||
local enabled_modules = utils.getenv("PORTAL_MODULES")
|
||||
|
||||
if not enabled_modules then
|
||||
return false
|
||||
end
|
||||
|
||||
return enabled_modules:find(module_abbr) ~= nil
|
||||
end
|
||||
|
||||
function _M.is_disabled(module_abbr)
|
||||
return not _M.is_enabled(module_abbr)
|
||||
end
|
||||
|
||||
return _M
|
|
@ -0,0 +1,95 @@
|
|||
-- luacheck: ignore os
|
||||
|
||||
local skynet_modules = require("skynet.modules")
|
||||
|
||||
describe("is_enabled", function()
|
||||
before_each(function()
|
||||
stub(os, "getenv")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.getenv:revert()
|
||||
end)
|
||||
|
||||
it("should return false if PORTAL_MODULES are not defined", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns(nil)
|
||||
|
||||
assert.is_false(skynet_modules.is_enabled("a"))
|
||||
end)
|
||||
|
||||
it("should return false if PORTAL_MODULES are empty", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns("")
|
||||
|
||||
assert.is_false(skynet_modules.is_enabled("a"))
|
||||
end)
|
||||
|
||||
it("should return false if module is not enabled", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns("qwerty")
|
||||
|
||||
assert.is_false(skynet_modules.is_enabled("a"))
|
||||
end)
|
||||
|
||||
it("should return true if module is enabled", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns("asdfg")
|
||||
|
||||
assert.is_true(skynet_modules.is_enabled("a"))
|
||||
end)
|
||||
|
||||
it("should throw an error for empty module", function()
|
||||
assert.has_error(function()
|
||||
skynet_modules.is_enabled()
|
||||
end, "Module abbreviation 'nil' should be exactly one character long string")
|
||||
end)
|
||||
|
||||
it("should throw an error for too long module", function()
|
||||
assert.has_error(function()
|
||||
skynet_modules.is_enabled("gandalf")
|
||||
end, "Module abbreviation 'gandalf' should be exactly one character long string")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("is_disabled", function()
|
||||
before_each(function()
|
||||
stub(os, "getenv")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.getenv:revert()
|
||||
end)
|
||||
|
||||
it("should return true if PORTAL_MODULES are not defined", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns(nil)
|
||||
|
||||
assert.is_true(skynet_modules.is_disabled("a"))
|
||||
end)
|
||||
|
||||
it("should return true if PORTAL_MODULES are empty", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns("")
|
||||
|
||||
assert.is_true(skynet_modules.is_disabled("a"))
|
||||
end)
|
||||
|
||||
it("should return true if module is not enabled", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns("qwerty")
|
||||
|
||||
assert.is_true(skynet_modules.is_disabled("a"))
|
||||
end)
|
||||
|
||||
it("should return false if module is enabled", function()
|
||||
os.getenv.on_call_with("PORTAL_MODULES").returns("asdfg")
|
||||
|
||||
assert.is_false(skynet_modules.is_disabled("a"))
|
||||
end)
|
||||
|
||||
it("should throw an error for empty module", function()
|
||||
assert.has_error(function()
|
||||
skynet_modules.is_disabled()
|
||||
end, "Module abbreviation 'nil' should be exactly one character long string")
|
||||
end)
|
||||
|
||||
it("should throw an error for too long module", function()
|
||||
assert.has_error(function()
|
||||
skynet_modules.is_disabled("gandalf")
|
||||
end, "Module abbreviation 'gandalf' should be exactly one character long string")
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,26 @@
|
|||
local _M = {}
|
||||
|
||||
function _M.scan_skylink_timer(premature, skylink)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
|
||||
-- 10.10.10.101 points to malware-scanner service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.101:4000/scan/" .. skylink, {
|
||||
method = "POST",
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= ngx.HTTP_OK) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed malware-scanner request /scan/" .. skylink .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
function _M.scan_skylink(skylink)
|
||||
if not skylink then return end
|
||||
|
||||
local ok, err = ngx.timer.at(0, _M.scan_skylink_timer, skylink)
|
||||
if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
|
||||
return _M
|
|
@ -0,0 +1,119 @@
|
|||
-- luacheck: ignore ngx
|
||||
|
||||
local skynet_scanner = require("skynet.scanner")
|
||||
local skylink = "AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA"
|
||||
|
||||
describe("scan_skylink", function()
|
||||
before_each(function()
|
||||
stub(ngx.timer, "at")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
ngx.timer.at:revert()
|
||||
end)
|
||||
|
||||
it("should schedule a timer when skylink is provided", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_scanner.scan_skylink(skylink)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(0, skynet_scanner.scan_skylink_timer, skylink)
|
||||
end)
|
||||
|
||||
it("should log an error if timer failed to create", function()
|
||||
stub(ngx, "log")
|
||||
|
||||
ngx.timer.at.invokes(function() return false, "such a failure" end)
|
||||
|
||||
skynet_scanner.scan_skylink(skylink)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(0, skynet_scanner.scan_skylink_timer, skylink)
|
||||
assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure")
|
||||
|
||||
ngx.log:revert()
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if skylink is not provided", function()
|
||||
skynet_scanner.scan_skylink()
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("scan_skylink_timer", function()
|
||||
before_each(function()
|
||||
stub(ngx, "log")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
local resty_http = require("resty.http")
|
||||
|
||||
ngx.log:revert()
|
||||
resty_http.new:revert()
|
||||
end)
|
||||
|
||||
it("should exit early on premature", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new()
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_scanner.scan_skylink_timer(true, skylink)
|
||||
|
||||
assert.stub(request_uri).was_not_called()
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should make a post request with skylink to scanner service", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 200 } -- return 200 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_scanner.scan_skylink_timer(false, skylink)
|
||||
|
||||
local uri = "http://10.10.10.101:4000/scan/" .. skylink
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST" })
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should log message on scanner request failure with response code", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 404, body = "baz" } -- return 404 failure
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_scanner.scan_skylink_timer(false, skylink)
|
||||
|
||||
local uri = "http://10.10.10.101:4000/scan/" .. skylink
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST" })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed malware-scanner request /scan/AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA: ",
|
||||
"[HTTP 404] baz"
|
||||
)
|
||||
end)
|
||||
|
||||
it("should log message on scanner request error", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return nil, "foo != bar" -- return error
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_scanner.scan_skylink_timer(false, skylink)
|
||||
|
||||
local uri = "http://10.10.10.101:4000/scan/" .. skylink
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST" })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed malware-scanner request /scan/AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA: ",
|
||||
"foo != bar"
|
||||
)
|
||||
end)
|
||||
end)
|
|
@ -0,0 +1,91 @@
|
|||
local _M = {}
|
||||
|
||||
local utils = require("utils")
|
||||
|
||||
function _M.track_download_timer(premature, skylink, status, auth_headers, body_bytes_sent)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
local query = table.concat({ "status=" .. status, "bytes=" .. body_bytes_sent }, "&")
|
||||
|
||||
-- 10.10.10.70 points to accounts service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/track/download/" .. skylink .. "?" .. query, {
|
||||
method = "POST",
|
||||
headers = auth_headers,
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= 204) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed accounts service request /track/download/" .. skylink .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
function _M.track_download(skylink, status_code, auth_headers, body_bytes_sent)
|
||||
local has_auth_headers = not utils.is_table_empty(auth_headers)
|
||||
local status_success = status_code >= 200 and status_code <= 299
|
||||
|
||||
if skylink and status_success and has_auth_headers then
|
||||
local ok, err = ngx.timer.at(0, _M.track_download_timer, skylink, status_code, auth_headers, body_bytes_sent)
|
||||
if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
|
||||
function _M.track_upload_timer(premature, skylink, auth_headers)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
|
||||
-- 10.10.10.70 points to accounts service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/track/upload/" .. skylink, {
|
||||
method = "POST",
|
||||
headers = auth_headers,
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= 204) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed accounts service request /track/upload/" .. skylink .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
function _M.track_upload(skylink, status_code, auth_headers)
|
||||
local has_auth_headers = not utils.is_table_empty(auth_headers)
|
||||
local status_success = status_code >= 200 and status_code <= 299
|
||||
|
||||
if skylink and status_success and has_auth_headers then
|
||||
local ok, err = ngx.timer.at(0, _M.track_upload_timer, skylink, auth_headers)
|
||||
if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
|
||||
function _M.track_registry_timer(premature, auth_headers, request_method)
|
||||
if premature then return end
|
||||
|
||||
local httpc = require("resty.http").new()
|
||||
|
||||
-- based on request method we assign a registry action string used
|
||||
-- in track endpoint namely "read" for GET and "write" for POST
|
||||
local registry_action = request_method == "GET" and "read" or "write"
|
||||
|
||||
-- 10.10.10.70 points to accounts service (alias not available when using resty-http)
|
||||
local res, err = httpc:request_uri("http://10.10.10.70:3000/track/registry/" .. registry_action, {
|
||||
method = "POST",
|
||||
headers = auth_headers,
|
||||
})
|
||||
|
||||
if err or (res and res.status ~= 204) then
|
||||
local error_response = err or ("[HTTP " .. res.status .. "] " .. res.body)
|
||||
ngx.log(ngx.ERR, "Failed accounts service request /track/registry/" .. registry_action .. ": ", error_response)
|
||||
end
|
||||
end
|
||||
|
||||
function _M.track_registry(status_code, auth_headers, request_method)
|
||||
local has_auth_headers = not utils.is_table_empty(auth_headers)
|
||||
local tracked_status = status_code == 200 or status_code == 404
|
||||
|
||||
if tracked_status and has_auth_headers then
|
||||
local ok, err = ngx.timer.at(0, _M.track_registry_timer, auth_headers, request_method)
|
||||
if not ok then ngx.log(ngx.ERR, "Failed to create timer: ", err) end
|
||||
end
|
||||
end
|
||||
|
||||
return _M
|
|
@ -0,0 +1,550 @@
|
|||
-- luacheck: ignore ngx
|
||||
|
||||
local skynet_tracker = require("skynet.tracker")
|
||||
|
||||
local valid_skylink = "AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA"
|
||||
local valid_status_code = 200
|
||||
local valid_auth_headers = { ["Skynet-Api-Key"] = "foo" }
|
||||
|
||||
describe("track_download", function()
|
||||
local valid_body_bytes_sent = 12345
|
||||
|
||||
before_each(function()
|
||||
stub(ngx.timer, "at")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
ngx.timer.at:revert()
|
||||
end)
|
||||
|
||||
it("should schedule a timer when conditions are met", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_download(valid_skylink, valid_status_code, valid_auth_headers, valid_body_bytes_sent)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(
|
||||
0,
|
||||
skynet_tracker.track_download_timer,
|
||||
valid_skylink,
|
||||
valid_status_code,
|
||||
valid_auth_headers,
|
||||
valid_body_bytes_sent
|
||||
)
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if skylink is empty", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_download(nil, valid_status_code, valid_auth_headers, valid_body_bytes_sent)
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if status code is not in 2XX range", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
-- couple of example of 4XX and 5XX codes
|
||||
skynet_tracker.track_download(valid_skylink, 401, valid_auth_headers, valid_body_bytes_sent)
|
||||
skynet_tracker.track_download(valid_skylink, 403, valid_auth_headers, valid_body_bytes_sent)
|
||||
skynet_tracker.track_download(valid_skylink, 490, valid_auth_headers, valid_body_bytes_sent)
|
||||
skynet_tracker.track_download(valid_skylink, 500, valid_auth_headers, valid_body_bytes_sent)
|
||||
skynet_tracker.track_download(valid_skylink, 502, valid_auth_headers, valid_body_bytes_sent)
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if auth headers are empty", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_download(valid_skylink, valid_status_code, {}, valid_body_bytes_sent)
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should log an error if timer failed to create", function()
|
||||
stub(ngx, "log")
|
||||
ngx.timer.at.invokes(function() return false, "such a failure" end)
|
||||
|
||||
skynet_tracker.track_download(valid_skylink, valid_status_code, valid_auth_headers, valid_body_bytes_sent)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(
|
||||
0,
|
||||
skynet_tracker.track_download_timer,
|
||||
valid_skylink,
|
||||
valid_status_code,
|
||||
valid_auth_headers,
|
||||
valid_body_bytes_sent
|
||||
)
|
||||
assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure")
|
||||
|
||||
ngx.log:revert()
|
||||
end)
|
||||
|
||||
describe("track_download_timer", function()
|
||||
before_each(function()
|
||||
stub(ngx, "log")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
local resty_http = require("resty.http")
|
||||
|
||||
ngx.log:revert()
|
||||
resty_http.new:revert()
|
||||
end)
|
||||
|
||||
it("should exit early on premature", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 200 } -- return 200 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_download_timer(
|
||||
true,
|
||||
valid_skylink,
|
||||
valid_status_code,
|
||||
valid_auth_headers,
|
||||
valid_body_bytes_sent
|
||||
)
|
||||
|
||||
assert.stub(request_uri).was_not_called()
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should make a post request to tracker service", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 204 } -- return 204 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_download_timer(
|
||||
false,
|
||||
valid_skylink,
|
||||
valid_status_code,
|
||||
valid_auth_headers,
|
||||
valid_body_bytes_sent
|
||||
)
|
||||
|
||||
local uri_params = "status=" .. valid_status_code .. "&bytes=" .. valid_body_bytes_sent
|
||||
local uri = "http://10.10.10.70:3000/track/download/" .. valid_skylink .. "?" .. uri_params
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should log message on tracker request failure with response code", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 404, body = "baz" } -- return 404 failure
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_download_timer(
|
||||
false,
|
||||
valid_skylink,
|
||||
valid_status_code,
|
||||
valid_auth_headers,
|
||||
valid_body_bytes_sent
|
||||
)
|
||||
|
||||
local uri_params = "status=" .. valid_status_code .. "&bytes=" .. valid_body_bytes_sent
|
||||
local uri = "http://10.10.10.70:3000/track/download/" .. valid_skylink .. "?" .. uri_params
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed accounts service request /track/download/" .. valid_skylink .. ": ",
|
||||
"[HTTP 404] baz"
|
||||
)
|
||||
end)
|
||||
|
||||
it("should log message on tracker request error", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return nil, "foo != bar" -- return error
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_download_timer(
|
||||
false,
|
||||
valid_skylink,
|
||||
valid_status_code,
|
||||
valid_auth_headers,
|
||||
valid_body_bytes_sent
|
||||
)
|
||||
|
||||
local uri_params = "status=" .. valid_status_code .. "&bytes=" .. valid_body_bytes_sent
|
||||
local uri = "http://10.10.10.70:3000/track/download/" .. valid_skylink .. "?" .. uri_params
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed accounts service request /track/download/" .. valid_skylink .. ": ",
|
||||
"foo != bar"
|
||||
)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("track_upload", function()
|
||||
before_each(function()
|
||||
stub(ngx.timer, "at")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
ngx.timer.at:revert()
|
||||
end)
|
||||
|
||||
it("should schedule a timer when conditions are met", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_upload(valid_skylink, valid_status_code, valid_auth_headers)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(
|
||||
0,
|
||||
skynet_tracker.track_upload_timer,
|
||||
valid_skylink,
|
||||
valid_auth_headers
|
||||
)
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if skylink is empty", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_upload(nil, valid_status_code, valid_auth_headers)
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if status code is not in 2XX range", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
-- couple of example of 4XX and 5XX codes
|
||||
skynet_tracker.track_upload(valid_skylink, 401, valid_auth_headers)
|
||||
skynet_tracker.track_upload(valid_skylink, 403, valid_auth_headers)
|
||||
skynet_tracker.track_upload(valid_skylink, 490, valid_auth_headers)
|
||||
skynet_tracker.track_upload(valid_skylink, 500, valid_auth_headers)
|
||||
skynet_tracker.track_upload(valid_skylink, 502, valid_auth_headers)
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if auth headers are empty", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_upload(valid_skylink, valid_status_code, {})
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should log an error if timer failed to create", function()
|
||||
stub(ngx, "log")
|
||||
ngx.timer.at.invokes(function() return false, "such a failure" end)
|
||||
|
||||
skynet_tracker.track_upload(valid_skylink, valid_status_code, valid_auth_headers)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(
|
||||
0,
|
||||
skynet_tracker.track_upload_timer,
|
||||
valid_skylink,
|
||||
valid_auth_headers
|
||||
)
|
||||
assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure")
|
||||
|
||||
ngx.log:revert()
|
||||
end)
|
||||
|
||||
describe("track_upload_timer", function()
|
||||
before_each(function()
|
||||
stub(ngx, "log")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
local resty_http = require("resty.http")
|
||||
|
||||
ngx.log:revert()
|
||||
resty_http.new:revert()
|
||||
end)
|
||||
|
||||
it("should exit early on premature", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 200 } -- return 200 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_upload_timer(
|
||||
true,
|
||||
valid_skylink,
|
||||
valid_auth_headers
|
||||
)
|
||||
|
||||
assert.stub(request_uri).was_not_called()
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should make a post request to tracker service", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 204 } -- return 204 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_upload_timer(
|
||||
false,
|
||||
valid_skylink,
|
||||
valid_auth_headers
|
||||
)
|
||||
|
||||
local uri = "http://10.10.10.70:3000/track/upload/" .. valid_skylink
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should log message on tracker request failure with response code", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 404, body = "baz" } -- return 404 failure
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_upload_timer(
|
||||
false,
|
||||
valid_skylink,
|
||||
valid_auth_headers
|
||||
)
|
||||
|
||||
local uri = "http://10.10.10.70:3000/track/upload/" .. valid_skylink
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed accounts service request /track/upload/" .. valid_skylink .. ": ",
|
||||
"[HTTP 404] baz"
|
||||
)
|
||||
end)
|
||||
|
||||
it("should log message on tracker request error", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return nil, "foo != bar" -- return error
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_upload_timer(
|
||||
false,
|
||||
valid_skylink,
|
||||
valid_auth_headers
|
||||
)
|
||||
|
||||
local uri = "http://10.10.10.70:3000/track/upload/" .. valid_skylink
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed accounts service request /track/upload/" .. valid_skylink .. ": ",
|
||||
"foo != bar"
|
||||
)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("track_registry", function()
|
||||
local status_code_ok = 200
|
||||
local status_code_not_found = 404
|
||||
local request_method_write = "POST"
|
||||
local request_method_read = "GET"
|
||||
|
||||
before_each(function()
|
||||
stub(ngx.timer, "at")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
ngx.timer.at:revert()
|
||||
end)
|
||||
|
||||
it("should schedule a timer when status code was 200", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_registry(status_code_ok, valid_auth_headers, request_method_write)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(
|
||||
0,
|
||||
skynet_tracker.track_registry_timer,
|
||||
valid_auth_headers,
|
||||
request_method_write
|
||||
)
|
||||
end)
|
||||
|
||||
it("should schedule a timer when status code was 404", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_registry(status_code_not_found, valid_auth_headers, request_method_write)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(
|
||||
0,
|
||||
skynet_tracker.track_registry_timer,
|
||||
valid_auth_headers,
|
||||
request_method_write
|
||||
)
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if status code is not in 200 or 404", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
-- couple of example of invalid 2XX, 4XX and 5XX codes
|
||||
skynet_tracker.track_registry(204, valid_auth_headers, request_method_write)
|
||||
skynet_tracker.track_registry(206, valid_auth_headers, request_method_write)
|
||||
skynet_tracker.track_registry(401, valid_auth_headers, request_method_write)
|
||||
skynet_tracker.track_registry(403, valid_auth_headers, request_method_write)
|
||||
skynet_tracker.track_registry(490, valid_auth_headers, request_method_write)
|
||||
skynet_tracker.track_registry(500, valid_auth_headers, request_method_write)
|
||||
skynet_tracker.track_registry(502, valid_auth_headers, request_method_write)
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should not schedule a timer if auth headers are empty", function()
|
||||
ngx.timer.at.invokes(function() return true, nil end)
|
||||
|
||||
skynet_tracker.track_registry(status_code_ok, {}, request_method_write)
|
||||
|
||||
assert.stub(ngx.timer.at).was_not_called()
|
||||
end)
|
||||
|
||||
it("should log an error if timer failed to create", function()
|
||||
stub(ngx, "log")
|
||||
ngx.timer.at.invokes(function() return false, "such a failure" end)
|
||||
|
||||
skynet_tracker.track_registry(status_code_ok, valid_auth_headers, request_method_write)
|
||||
|
||||
assert.stub(ngx.timer.at).was_called_with(
|
||||
0,
|
||||
skynet_tracker.track_registry_timer,
|
||||
valid_auth_headers,
|
||||
request_method_write
|
||||
)
|
||||
assert.stub(ngx.log).was_called_with(ngx.ERR, "Failed to create timer: ", "such a failure")
|
||||
|
||||
ngx.log:revert()
|
||||
end)
|
||||
|
||||
describe("track_registry_timer", function()
|
||||
before_each(function()
|
||||
stub(ngx, "log")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
local resty_http = require("resty.http")
|
||||
|
||||
ngx.log:revert()
|
||||
resty_http.new:revert()
|
||||
end)
|
||||
|
||||
it("should exit early on premature", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 200 } -- return 200 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_registry_timer(
|
||||
true,
|
||||
valid_auth_headers,
|
||||
request_method_write
|
||||
)
|
||||
|
||||
assert.stub(request_uri).was_not_called()
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should make a post request to registry write tracker service", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 204 } -- return 204 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_registry_timer(
|
||||
false,
|
||||
valid_auth_headers,
|
||||
request_method_write
|
||||
)
|
||||
|
||||
local uri = "http://10.10.10.70:3000/track/registry/write"
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should make a post request to registry read tracker service", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 204 } -- return 204 success
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_registry_timer(
|
||||
false,
|
||||
valid_auth_headers,
|
||||
request_method_read
|
||||
)
|
||||
|
||||
local uri = "http://10.10.10.70:3000/track/registry/read"
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_not_called()
|
||||
end)
|
||||
|
||||
it("should log message on tracker request failure with response code", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return { status = 404, body = "baz" } -- return 404 failure
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_registry_timer(
|
||||
false,
|
||||
valid_auth_headers,
|
||||
request_method_write
|
||||
)
|
||||
|
||||
local uri = "http://10.10.10.70:3000/track/registry/write"
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed accounts service request /track/registry/write: ",
|
||||
"[HTTP 404] baz"
|
||||
)
|
||||
end)
|
||||
|
||||
it("should log message on tracker request error", function()
|
||||
local resty_http = require("resty.http")
|
||||
local request_uri = spy.new(function()
|
||||
return nil, "foo != bar" -- return error
|
||||
end)
|
||||
local httpc = mock({ request_uri = request_uri })
|
||||
stub(resty_http, "new").returns(httpc)
|
||||
|
||||
skynet_tracker.track_registry_timer(
|
||||
false,
|
||||
valid_auth_headers,
|
||||
request_method_write
|
||||
)
|
||||
|
||||
local uri = "http://10.10.10.70:3000/track/registry/write"
|
||||
assert.stub(request_uri).was_called_with(httpc, uri, { method = "POST", headers = valid_auth_headers })
|
||||
assert.stub(ngx.log).was_called_with(
|
||||
ngx.ERR,
|
||||
"Failed accounts service request /track/registry/write: ",
|
||||
"foo != bar"
|
||||
)
|
||||
end)
|
||||
end)
|
||||
end)
|
|
@ -1,13 +1,20 @@
|
|||
local _M = {}
|
||||
|
||||
local ngx_base64 = require("ngx.base64")
|
||||
local utils = require("utils")
|
||||
|
||||
function _M.authorization_header()
|
||||
-- read api password from env variable
|
||||
local apipassword = os.getenv("SIA_API_PASSWORD")
|
||||
local apipassword = utils.getenv("SIA_API_PASSWORD")
|
||||
-- if api password is not available as env variable, read it from disk
|
||||
if apipassword == nil or apipassword == "" then
|
||||
if not apipassword then
|
||||
-- open apipassword file for reading (b flag is required for some reason)
|
||||
-- (file /etc/.sia/apipassword has to be mounted from the host system)
|
||||
local apipassword_file = io.open("/data/sia/apipassword", "rb")
|
||||
-- make sure to throw a meaningful error if apipassword file does not exist
|
||||
if not apipassword_file then
|
||||
error("Error reading /data/sia/apipassword file")
|
||||
end
|
||||
-- read apipassword file contents and trim newline (important)
|
||||
apipassword = apipassword_file:read("*all"):gsub("%s+", "")
|
||||
-- make sure to close file after reading the password
|
||||
|
@ -15,7 +22,7 @@ function _M.authorization_header()
|
|||
end
|
||||
-- encode the user:password authorization string
|
||||
-- (in our case user is empty so it is just :password)
|
||||
local content = require("ngx.base64").encode_base64url(":" .. apipassword)
|
||||
local content = ngx_base64.encode_base64url(":" .. apipassword)
|
||||
-- set authorization header with proper base64 encoded string
|
||||
return "Basic " .. content
|
||||
end
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
-- luacheck: ignore io
|
||||
|
||||
local utils = require('utils')
|
||||
local skynet_utils = require('skynet.utils')
|
||||
|
||||
describe("authorization_header", function()
|
||||
local apipassword = "ddd0c1430fbf2708"
|
||||
local expected_header = "Basic OmRkZDBjMTQzMGZiZjI3MDg"
|
||||
|
||||
it("reads SIA_API_PASSWORD from env variable and returns a header", function()
|
||||
-- stub getenv on SIA_API_PASSWORD
|
||||
stub(utils, "getenv")
|
||||
utils.getenv.on_call_with("SIA_API_PASSWORD").returns(apipassword)
|
||||
|
||||
local header = skynet_utils.authorization_header()
|
||||
|
||||
assert.is_equal(header, expected_header)
|
||||
|
||||
-- revert stub to original function
|
||||
utils.getenv:revert()
|
||||
end)
|
||||
|
||||
it("uses /data/sia/apipassword file if SIA_API_PASSWORD env var is missing", function()
|
||||
-- stub /data/sia/apipassword file
|
||||
stub(io, "open")
|
||||
io.open.on_call_with("/data/sia/apipassword", "rb").returns(mock({
|
||||
read = spy.new(function() return apipassword end),
|
||||
close = spy.new()
|
||||
}))
|
||||
|
||||
local header = skynet_utils.authorization_header()
|
||||
|
||||
assert.is_equal(header, expected_header)
|
||||
|
||||
-- revert stub to original function
|
||||
io.open:revert()
|
||||
end)
|
||||
|
||||
it("should choose env variable over file if both are available", function()
|
||||
-- stub getenv on SIA_API_PASSWORD
|
||||
stub(utils, "getenv")
|
||||
utils.getenv.on_call_with("SIA_API_PASSWORD").returns(apipassword)
|
||||
|
||||
-- stub /data/sia/apipassword file
|
||||
stub(io, "open")
|
||||
io.open.on_call_with("/data/sia/apipassword", "rb").returns(mock({
|
||||
read = spy.new(function() return "foooooooooooooo" end),
|
||||
close = spy.new()
|
||||
}))
|
||||
|
||||
local header = skynet_utils.authorization_header()
|
||||
|
||||
assert.is_equal(header, "Basic OmRkZDBjMTQzMGZiZjI3MDg")
|
||||
|
||||
-- revert stubs to original function
|
||||
utils.getenv:revert()
|
||||
io.open:revert()
|
||||
end)
|
||||
|
||||
it("should error out if neither env variable is available nor file exists", function()
|
||||
assert.has_error(function()
|
||||
skynet_utils.authorization_header()
|
||||
end, "Error reading /data/sia/apipassword file")
|
||||
end)
|
||||
end)
|
|
@ -42,4 +42,42 @@ function _M.extract_cookie_value(cookie_string, name_matcher)
|
|||
return string.sub(cookie, value_start)
|
||||
end
|
||||
|
||||
-- utility function that builds on os.getenv to get environment variable value
|
||||
-- * will always return nil for both unset and empty env vars
|
||||
-- * parse "boolean": "true" and "1" as true, "false" and "0" as false, throws for others
|
||||
-- * parse "integer": any numerical string gets converted, otherwise returns nil
|
||||
function _M.getenv(name, parse)
|
||||
local value = os.getenv(name)
|
||||
|
||||
-- treat empty string value as nil to simplify comparisons
|
||||
if value == nil or value == "" then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- do not parse the value
|
||||
if parse == nil then
|
||||
return value
|
||||
end
|
||||
|
||||
-- try to parse as boolean
|
||||
if parse == "boolean" then
|
||||
if string.lower(value) == "true" or value == "1" then
|
||||
return true
|
||||
end
|
||||
|
||||
if string.lower(value) == "false" or value == "0" then
|
||||
return false
|
||||
end
|
||||
|
||||
error("utils.getenv: Parsing value '" .. tostring(value) .. "' to boolean is not supported")
|
||||
end
|
||||
|
||||
-- try to parse as integer
|
||||
if parse == "integer" then
|
||||
return tonumber(value)
|
||||
end
|
||||
|
||||
error("utils.getenv: Parsing to '" .. parse .. "' is not supported")
|
||||
end
|
||||
|
||||
return _M
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
-- luacheck: ignore os
|
||||
|
||||
local utils = require('utils')
|
||||
|
||||
describe("is_table_empty", function()
|
||||
|
@ -77,3 +79,137 @@ describe("extract_cookie_value", function()
|
|||
assert.are.equals(value, "MTY0NzUyr8jD-ytiWtspm0tGabKfooxeIDuWcXhJ3lnY0eEw==")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("getenv", function()
|
||||
before_each(function()
|
||||
stub(os, "getenv")
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
os.getenv:revert()
|
||||
end)
|
||||
|
||||
it("should return nil for not existing env var", function()
|
||||
os.getenv.on_call_with("foo").returns(nil)
|
||||
|
||||
assert.is_nil(utils.getenv("foo"))
|
||||
end)
|
||||
|
||||
it("should return nil for env var that is an empty string", function()
|
||||
os.getenv.on_call_with("foo").returns("")
|
||||
|
||||
assert.is_nil(utils.getenv("foo"))
|
||||
end)
|
||||
|
||||
it("should return the value as is when it is non empty string", function()
|
||||
os.getenv.on_call_with("foo").returns("bar")
|
||||
|
||||
assert.is_equal(utils.getenv("foo"), "bar")
|
||||
end)
|
||||
|
||||
describe("parse", function()
|
||||
it("should throw on not supported parser", function()
|
||||
os.getenv.on_call_with("foo").returns("test")
|
||||
|
||||
assert.has_error(function()
|
||||
utils.getenv("foo", "starwars")
|
||||
end, "utils.getenv: Parsing to 'starwars' is not supported")
|
||||
end)
|
||||
|
||||
describe("as boolean", function()
|
||||
it("should return nil for not existing env var", function()
|
||||
os.getenv.on_call_with("foo").returns(nil)
|
||||
|
||||
assert.is_nil(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should return nil for env var that is an empty string", function()
|
||||
os.getenv.on_call_with("foo").returns("")
|
||||
|
||||
assert.is_nil(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should parse 'true' string as true", function()
|
||||
os.getenv.on_call_with("foo").returns("true")
|
||||
|
||||
assert.is_true(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should parse 'True' string as true", function()
|
||||
os.getenv.on_call_with("foo").returns("True")
|
||||
|
||||
assert.is_true(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should parse '1' string as true", function()
|
||||
os.getenv.on_call_with("foo").returns("1")
|
||||
|
||||
assert.is_true(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should parse 'false' string as false", function()
|
||||
os.getenv.on_call_with("foo").returns("false")
|
||||
|
||||
assert.is_false(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should parse 'False' string as false", function()
|
||||
os.getenv.on_call_with("foo").returns("False")
|
||||
|
||||
assert.is_false(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should parse '0' string as false", function()
|
||||
os.getenv.on_call_with("foo").returns("0")
|
||||
|
||||
assert.is_false(utils.getenv("foo", "boolean"))
|
||||
end)
|
||||
|
||||
it("should throw an error for not supported string", function()
|
||||
os.getenv.on_call_with("foo").returns("mandalorian")
|
||||
|
||||
assert.has_error(function()
|
||||
utils.getenv("foo", "boolean")
|
||||
end, "utils.getenv: Parsing value 'mandalorian' to boolean is not supported")
|
||||
end)
|
||||
end)
|
||||
|
||||
describe("as integer", function()
|
||||
it("should return nil for not existing env var", function()
|
||||
os.getenv.on_call_with("foo").returns(nil)
|
||||
|
||||
assert.is_nil(utils.getenv("foo", "integer"))
|
||||
end)
|
||||
|
||||
it("should return nil for env var that is an empty string", function()
|
||||
os.getenv.on_call_with("foo").returns("")
|
||||
|
||||
assert.is_nil(utils.getenv("foo", "integer"))
|
||||
end)
|
||||
|
||||
it("should parse '0' string as 0", function()
|
||||
os.getenv.on_call_with("foo").returns("0")
|
||||
|
||||
assert.equals(utils.getenv("foo", "integer"), 0)
|
||||
end)
|
||||
|
||||
it("should parse '1' string as 1", function()
|
||||
os.getenv.on_call_with("foo").returns("1")
|
||||
|
||||
assert.equals(utils.getenv("foo", "integer"), 1)
|
||||
end)
|
||||
|
||||
it("should parse '-1' string as -1", function()
|
||||
os.getenv.on_call_with("foo").returns("-1")
|
||||
|
||||
assert.equals(utils.getenv("foo", "integer"), -1)
|
||||
end)
|
||||
|
||||
it("should return nil for non numerical string", function()
|
||||
os.getenv.on_call_with("foo").returns("test")
|
||||
|
||||
assert.is_nil(utils.getenv("foo", "integer"))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
exclude = {
|
||||
"/usr/local/openresty", -- internal openresty libraries
|
||||
"rbusted", -- busted executable
|
||||
"basexx", -- external library https://github.com/aiq/basexx
|
||||
}
|
||||
includeuntestedfiles = true
|
|
@ -0,0 +1,11 @@
|
|||
FROM openresty/openresty:1.19.9.1-focal
|
||||
|
||||
WORKDIR /etc/nginx
|
||||
|
||||
RUN luarocks install lua-resty-http && \
|
||||
luarocks install hasher && \
|
||||
luarocks install busted
|
||||
|
||||
COPY rbusted /etc/nginx/
|
||||
|
||||
CMD /etc/nginx/rbusted --verbose --pattern=spec /usr/local/openresty/site/lualib
|
|
@ -0,0 +1,3 @@
|
|||
# Running tests locally
|
||||
|
||||
`docker run -v $(pwd)/docker/nginx/libs:/usr/local/openresty/site/lualib --rm -it $(docker build -q docker/nginx/testing)`
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env resty
|
||||
|
||||
setmetatable(_G, nil)
|
||||
|
||||
pcall(require, "luarocks.loader")
|
||||
|
||||
-- Busted command-line runner
|
||||
require "busted.runner"({ standalone = false })
|
|
@ -11,15 +11,20 @@ This is a Gatsby application. To run it locally, all you need is:
|
|||
|
||||
## Accessing remote APIs
|
||||
|
||||
To be able to log in on a local environment with your production credentials, you'll need to make the browser believe you're actually on the same domain, otherwise the browser will block the session cookie.
|
||||
To have a fully functioning local environment, you'll need to make the browser believe you're actually on the same domain as a working API (i.e. a remote dev or production server) -- otherwise the browser will block the session cookie.
|
||||
To do the trick, configure proper environment variables in the `.env.development` file.
|
||||
This file allows to easily control which domain name you want to use locally and which API you'd like to access.
|
||||
|
||||
To do the trick, edit your `/etc/hosts` file and add a record like this:
|
||||
Example:
|
||||
|
||||
```
|
||||
127.0.0.1 local.skynetpro.net
|
||||
```env
|
||||
GATSBY_PORTAL_DOMAIN=skynetfree.net # Use skynetfree.net APIs
|
||||
GATSBY_HOST=local.skynetfree.net # Address of your local build
|
||||
```
|
||||
|
||||
then run `yarn develop:secure` -- it will run `gatsby develop` with `--https --host=local.skynetpro.net -p=443` options.
|
||||
If you're on macOS, you may need to `sudo` the command to successfully bind to port `443`.
|
||||
> It's recommended to keep the 2LD the same, so any cookies dispatched by the API work without issues.
|
||||
|
||||
> **NOTE:** This should become easier once we have a docker image for the new dashboard.
|
||||
With the file configured, run `yarn develop:secure` -- it will run `gatsby develop` with `--https -p=443` options.
|
||||
If you're on macOS, you may need to `sudo` the command to successfully bind to port `443` (https).
|
||||
|
||||
Gatsby will automatically add a proper entry to your `/etc/hosts` file and clean it up when process exits.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { SWRConfig } from "swr";
|
||||
import "@fontsource/sora/300.css"; // light
|
||||
import "@fontsource/sora/400.css"; // normal
|
||||
import "@fontsource/sora/500.css"; // medium
|
||||
|
@ -6,6 +7,7 @@ import "@fontsource/sora/600.css"; // semibold
|
|||
import "@fontsource/source-sans-pro/400.css"; // normal
|
||||
import "@fontsource/source-sans-pro/600.css"; // semibold
|
||||
import "./src/styles/global.css";
|
||||
import swrConfig from "./src/lib/swrConfig";
|
||||
import { MODAL_ROOT_ID } from "./src/components/Modal";
|
||||
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
|
||||
|
||||
|
@ -13,10 +15,12 @@ export function wrapPageElement({ element, props }) {
|
|||
const Layout = element.type.Layout ?? React.Fragment;
|
||||
return (
|
||||
<PortalSettingsProvider>
|
||||
<Layout {...props}>
|
||||
{element}
|
||||
<div id={MODAL_ROOT_ID} />
|
||||
</Layout>
|
||||
<SWRConfig value={swrConfig}>
|
||||
<Layout {...props}>
|
||||
{element}
|
||||
<div id={MODAL_ROOT_ID} />
|
||||
</Layout>
|
||||
</SWRConfig>
|
||||
</PortalSettingsProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
require("dotenv").config({
|
||||
path: `.env.${process.env.NODE_ENV}`,
|
||||
});
|
||||
|
||||
const { createProxyMiddleware } = require("http-proxy-middleware");
|
||||
|
||||
const { GATSBY_PORTAL_DOMAIN } = process.env;
|
||||
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: "Skynet Account",
|
||||
siteUrl: `https://account.${process.env.GATSBY_PORTAL_DOMAIN}/`,
|
||||
title: `Account Dashboard`,
|
||||
siteUrl: `https://account.${GATSBY_PORTAL_DOMAIN}`,
|
||||
},
|
||||
trailingSlash: "never",
|
||||
plugins: [
|
||||
|
@ -24,13 +30,27 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
developMiddleware: (app) => {
|
||||
// Proxy Accounts service API requests:
|
||||
app.use(
|
||||
"/api/",
|
||||
createProxyMiddleware({
|
||||
target: "https://account.skynetpro.net",
|
||||
target: `https://account.${GATSBY_PORTAL_DOMAIN}`,
|
||||
secure: false, // Do not reject self-signed certificates.
|
||||
changeOrigin: true,
|
||||
})
|
||||
);
|
||||
|
||||
// Proxy /skynet requests (e.g. uploads)
|
||||
app.use(
|
||||
["/skynet", "/__internal/"],
|
||||
createProxyMiddleware({
|
||||
target: `https://${GATSBY_PORTAL_DOMAIN}`,
|
||||
secure: false, // Do not reject self-signed certificates.
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
"^/skynet": "",
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { SWRConfig } from "swr";
|
||||
import "@fontsource/sora/300.css"; // light
|
||||
import "@fontsource/sora/400.css"; // normal
|
||||
import "@fontsource/sora/500.css"; // medium
|
||||
|
@ -6,6 +7,7 @@ import "@fontsource/sora/600.css"; // semibold
|
|||
import "@fontsource/source-sans-pro/400.css"; // normal
|
||||
import "@fontsource/source-sans-pro/600.css"; // semibold
|
||||
import "./src/styles/global.css";
|
||||
import swrConfig from "./src/lib/swrConfig";
|
||||
import { MODAL_ROOT_ID } from "./src/components/Modal";
|
||||
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
|
||||
|
||||
|
@ -13,10 +15,12 @@ export function wrapPageElement({ element, props }) {
|
|||
const Layout = element.type.Layout ?? React.Fragment;
|
||||
return (
|
||||
<PortalSettingsProvider>
|
||||
<Layout {...props}>
|
||||
{element}
|
||||
<div id={MODAL_ROOT_ID} />
|
||||
</Layout>
|
||||
<SWRConfig value={swrConfig}>
|
||||
<Layout {...props}>
|
||||
{element}
|
||||
<div id={MODAL_ROOT_ID} />
|
||||
</Layout>
|
||||
</SWRConfig>
|
||||
</PortalSettingsProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
],
|
||||
"scripts": {
|
||||
"develop": "gatsby develop",
|
||||
"develop:secure": "gatsby develop --https --host=local.skynetpro.net -p=443",
|
||||
"develop:secure": "dotenv -e .env.development -- gatsby develop --https -p=443",
|
||||
"start": "gatsby develop",
|
||||
"build": "gatsby build",
|
||||
"serve": "gatsby serve",
|
||||
|
@ -60,6 +60,8 @@
|
|||
"babel-loader": "^8.2.3",
|
||||
"babel-plugin-preval": "^5.1.0",
|
||||
"babel-plugin-styled-components": "^2.0.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"dotenv-cli": "^5.1.0",
|
||||
"eslint": "^8.9.0",
|
||||
"eslint-config-react-app": "^7.0.0",
|
||||
"eslint-plugin-storybook": "^0.5.6",
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useCallback, useState } from "react";
|
|||
|
||||
import { Alert } from "../Alert";
|
||||
import { Button } from "../Button";
|
||||
import { AddSkylinkToAPIKeyForm } from "../forms/AddSkylinkToAPIKeyForm";
|
||||
import { AddSkylinkToSponsorKeyForm } from "../forms/AddSkylinkToSponsorKeyForm";
|
||||
import { CogIcon, TrashIcon } from "../Icons";
|
||||
import { Modal } from "../Modal";
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { useAPIKeyRemoval } from "./useAPIKeyRemoval";
|
|||
|
||||
export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
||||
const { id, name, createdAt, skylinks } = apiKey;
|
||||
const isPublic = apiKey.public === "true";
|
||||
const isSponsorKey = apiKey.public === "true";
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const onSkylinkListEdited = useCallback(() => {
|
||||
|
@ -53,9 +53,9 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
|||
}, [abortEdit]);
|
||||
|
||||
const skylinksNumber = skylinks?.length ?? 0;
|
||||
const isNotConfigured = isPublic && skylinksNumber === 0;
|
||||
const isNotConfigured = isSponsorKey && skylinksNumber === 0;
|
||||
const skylinksPhrasePrefix = skylinksNumber === 0 ? "No" : skylinksNumber;
|
||||
const skylinksPhrase = `${skylinksPhrasePrefix} ${skylinksNumber === 1 ? "skylink" : "skylinks"} configured`;
|
||||
const skylinksPhrase = `${skylinksPhrasePrefix} ${skylinksNumber === 1 ? "skylink" : "skylinks"} sponsored`;
|
||||
|
||||
return (
|
||||
<li
|
||||
|
@ -66,21 +66,23 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
|||
<span className="col-span-2 sm:col-span-1 flex items-center">
|
||||
<span className="flex flex-col">
|
||||
<span className={cn("truncate", { "text-palette-300": !name })}>{name || "unnamed key"}</span>
|
||||
<button
|
||||
onClick={promptEdit}
|
||||
className={cn("text-xs hover:underline decoration-dotted", {
|
||||
"text-error": isNotConfigured,
|
||||
"text-palette-400": !isNotConfigured,
|
||||
})}
|
||||
>
|
||||
{skylinksPhrase}
|
||||
</button>
|
||||
{isSponsorKey && (
|
||||
<button
|
||||
onClick={promptEdit}
|
||||
className={cn("text-xs hover:underline decoration-dotted", {
|
||||
"text-error": isNotConfigured,
|
||||
"text-palette-400": !isNotConfigured,
|
||||
})}
|
||||
>
|
||||
{skylinksPhrase}
|
||||
</button>
|
||||
)}
|
||||
</span>
|
||||
</span>
|
||||
<span className="col-span-2 my-4 border-t border-t-palette-200/50 sm:hidden" />
|
||||
<span className="text-palette-400">{dayjs(createdAt).format("MMM DD, YYYY")}</span>
|
||||
<span className="flex items-center justify-end">
|
||||
{isPublic && (
|
||||
{isSponsorKey && (
|
||||
<button
|
||||
title="Add or remove skylinks"
|
||||
aria-label="Add or remove skylinks"
|
||||
|
@ -119,7 +121,7 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
|||
)}
|
||||
{editInitiated && (
|
||||
<Modal onClose={closeEditModal} className="flex flex-col gap-4 text-center sm:px-8 sm:py-6">
|
||||
<h4>Covered skylinks</h4>
|
||||
<h4>Sponsored skylinks</h4>
|
||||
{skylinks?.length > 0 ? (
|
||||
<ul className="text-xs flex flex-col gap-2">
|
||||
{skylinks.map((skylink) => (
|
||||
|
@ -143,7 +145,7 @@ export const APIKey = ({ apiKey, onRemoved, onEdited, onRemovalError }) => {
|
|||
|
||||
<div className="flex flex-col gap-4">
|
||||
{error && <Alert $variant="error">{error}</Alert>}
|
||||
<AddSkylinkToAPIKeyForm addSkylink={addSkylink} />
|
||||
<AddSkylinkToSponsorKeyForm addSkylink={addSkylink} />
|
||||
</div>
|
||||
<div className="flex gap-4 justify-center mt-4">
|
||||
<Button onClick={closeEditModal}>Close</Button>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
|
||||
import { useUser } from "../../contexts/user";
|
||||
import { SimpleUploadIcon } from "../Icons";
|
||||
// import { SimpleUploadIcon } from "../Icons";
|
||||
|
||||
const AVATAR_PLACEHOLDER = "/images/avatar-placeholder.svg";
|
||||
|
||||
|
@ -20,6 +20,7 @@ export const AvatarUploader = (props) => {
|
|||
>
|
||||
<img src={imageUrl} className="w-[160px]" alt="" />
|
||||
</div>
|
||||
{/* TODO: uncomment when avatar uploads work
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
className="flex items-center gap-4 hover:underline decoration-1 decoration-dashed underline-offset-2 decoration-gray-400"
|
||||
|
@ -28,8 +29,8 @@ export const AvatarUploader = (props) => {
|
|||
>
|
||||
<SimpleUploadIcon size={20} className="shrink-0" /> Upload profile picture
|
||||
</button>
|
||||
{/* TODO: actual uploading */}
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import dayjs from "dayjs";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
|
||||
import { useUser } from "../../contexts/user";
|
||||
import useActivePlan from "../../hooks/useActivePlan";
|
||||
|
@ -28,17 +29,20 @@ const CurrentPlan = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col h-full">
|
||||
<h4>{activePlan.name}</h4>
|
||||
<div className="text-palette-400">
|
||||
{activePlan.price === 0 && <p>100GB without paying a dime! 🎉</p>}
|
||||
<div className="text-palette-400 justify-between flex flex-col grow">
|
||||
{activePlan.price === 0 && activePlan.limits && (
|
||||
<p>{prettyBytes(activePlan.limits.storageLimit, { binary: true })} without paying a dime! 🎉</p>
|
||||
)}
|
||||
{activePlan.price !== 0 &&
|
||||
(user.subscriptionCancelAtPeriodEnd ? (
|
||||
<p>Your subscription expires {dayjs(user.subscribedUntil).fromNow()}</p>
|
||||
) : (
|
||||
<p className="first-letter:uppercase">{dayjs(user.subscribedUntil).fromNow(true)} until the next payment</p>
|
||||
))}
|
||||
<LatestPayment user={user} />
|
||||
|
||||
{user.subscriptionStatus && <LatestPayment user={user} />}
|
||||
<SuggestedPlan plans={plans} activePlan={activePlan} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -44,7 +44,7 @@ const useUsageData = () => {
|
|||
};
|
||||
|
||||
const size = (bytes) => {
|
||||
const text = fileSize(bytes ?? 0, { maximumFractionDigits: 0 });
|
||||
const text = fileSize(bytes ?? 0, { maximumFractionDigits: 0, binary: true });
|
||||
const [value, unit] = text.split(" ");
|
||||
|
||||
return {
|
||||
|
|
|
@ -12,11 +12,13 @@ const BarTip = styled.span.attrs({
|
|||
})``;
|
||||
|
||||
const BarLabel = styled.span.attrs({
|
||||
className: "bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow",
|
||||
className: "usage-label bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow",
|
||||
})`
|
||||
right: max(0%, ${({ $percentage }) => 100 - $percentage}%);
|
||||
top: -0.5rem;
|
||||
transform: translateX(50%);
|
||||
${({ $percentage }) => `
|
||||
left: max(0%, ${$percentage}%);
|
||||
top: -0.5rem;
|
||||
transform: translateX(-${$percentage}%);
|
||||
`}
|
||||
`;
|
||||
|
||||
export const GraphBar = ({ value, limit, label }) => {
|
||||
|
|
|
@ -7,13 +7,10 @@ import { ChevronDownIcon } from "../Icons";
|
|||
|
||||
const dropDown = keyframes`
|
||||
0% {
|
||||
transform: scaleY(0);
|
||||
}
|
||||
80% {
|
||||
transform: scaleY(1.1);
|
||||
transform: rotateX(-90deg);
|
||||
}
|
||||
100% {
|
||||
transform: scaleY(1);
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
`;
|
||||
|
||||
|
@ -35,10 +32,11 @@ const Flyout = styled.div.attrs(({ open }) => ({
|
|||
bg-white shadow-md shadow-palette-200/50
|
||||
${open ? "visible" : "invisible"}`,
|
||||
}))`
|
||||
transform-origin: top center;
|
||||
animation: ${({ open }) =>
|
||||
open
|
||||
? css`
|
||||
${dropDown} 0.1s ease-in-out
|
||||
${dropDown} .15s ease-in-out forwards;
|
||||
`
|
||||
: "none"};
|
||||
`;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useMemo } from "react";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import dayjs from "dayjs";
|
||||
import { DATE_FORMAT } from "../../lib/config";
|
||||
|
||||
const parseFileName = (fileName) => {
|
||||
const lastDotIndex = Math.max(0, fileName.lastIndexOf(".")) || Infinity;
|
||||
|
@ -10,7 +11,7 @@ const parseFileName = (fileName) => {
|
|||
|
||||
const formatItem = ({ size, name: rawFileName, uploadedOn, downloadedOn, ...rest }) => {
|
||||
const [name, type] = parseFileName(rawFileName);
|
||||
const date = dayjs(uploadedOn || downloadedOn).format("MM/DD/YYYY; HH:MM");
|
||||
const date = dayjs(uploadedOn || downloadedOn).format(DATE_FORMAT);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
|
|
|
@ -1,8 +1,19 @@
|
|||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { PageContainer } from "../PageContainer";
|
||||
|
||||
const FooterLink = styled.a.attrs({
|
||||
className: "text-palette-400 underline decoration-dotted decoration-offset-4 decoration-1",
|
||||
rel: "noreferrer",
|
||||
target: "_blank",
|
||||
})``;
|
||||
|
||||
export const Footer = () => (
|
||||
<PageContainer className="font-content text-palette-300 py-4">
|
||||
<p>© Skynet Labs Inc. All rights reserved.</p>
|
||||
<p>
|
||||
Made by <FooterLink href="https://skynetlabs.com">Skynet Labs</FooterLink>. Open-sourced{" "}
|
||||
<FooterLink href="https://github.com/SkynetLabs/skynet-webportal">on Github</FooterLink>.
|
||||
</p>
|
||||
</PageContainer>
|
||||
);
|
||||
|
|
|
@ -2,5 +2,5 @@ import { Link } from "gatsby";
|
|||
import styled from "styled-components";
|
||||
|
||||
export default styled(Link).attrs({
|
||||
className: "text-primary underline-offset-3 decoration-dotted hover:text-primary-light hover:underline",
|
||||
className: "text-primary underline-offset-2 decoration-1 decoration-dotted hover:text-primary-light hover:underline",
|
||||
})``;
|
||||
|
|
|
@ -3,12 +3,13 @@ import useSWR from "swr";
|
|||
|
||||
import { Table, TableBody, TableCell, TableRow } from "../Table";
|
||||
import { ContainerLoadingIndicator } from "../LoadingIndicator";
|
||||
import useFormattedFilesData from "../FileList/useFormattedFilesData";
|
||||
|
||||
import useFormattedActivityData from "./useFormattedActivityData";
|
||||
import { ViewAllLink } from "./ViewAllLink";
|
||||
|
||||
export default function ActivityTable({ type }) {
|
||||
const { data, error } = useSWR(`user/${type}?pageSize=3`);
|
||||
const items = useFormattedActivityData(data?.items || []);
|
||||
const items = useFormattedFilesData(data?.items || []);
|
||||
|
||||
if (!items.length) {
|
||||
return (
|
||||
|
@ -22,20 +23,23 @@ export default function ActivityTable({ type }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Table style={{ tableLayout: "fixed" }}>
|
||||
<TableBody>
|
||||
{items.map(({ id, name, type, size, date, skylink }) => (
|
||||
<TableRow key={id}>
|
||||
<TableCell>{name}</TableCell>
|
||||
<TableCell className="w-[80px]">{type}</TableCell>
|
||||
<TableCell className="w-[80px]" align="right">
|
||||
{size}
|
||||
</TableCell>
|
||||
<TableCell className="w-[180px]">{date}</TableCell>
|
||||
<TableCell>{skylink}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<>
|
||||
<Table style={{ tableLayout: "fixed" }}>
|
||||
<TableBody>
|
||||
{items.map(({ id, name, type, size, date, skylink }) => (
|
||||
<TableRow key={id}>
|
||||
<TableCell>{name}</TableCell>
|
||||
<TableCell className="w-[80px]">{type}</TableCell>
|
||||
<TableCell className="w-[80px]" align="right">
|
||||
{size}
|
||||
</TableCell>
|
||||
<TableCell className="w-[180px]">{date}</TableCell>
|
||||
<TableCell>{skylink}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<ViewAllLink to={`/files?tab=${type}`} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,36 +1,13 @@
|
|||
import * as React from "react";
|
||||
import { Link } from "gatsby";
|
||||
|
||||
import { Panel } from "../Panel";
|
||||
import { Tab, TabPanel, Tabs } from "../Tabs";
|
||||
import { ArrowRightIcon } from "../Icons";
|
||||
|
||||
import ActivityTable from "./ActivityTable";
|
||||
|
||||
const ViewAllLink = (props) => (
|
||||
<Link className="inline-flex mt-6 items-center gap-3 ease-in-out hover:brightness-90" {...props}>
|
||||
<span className="bg-primary rounded-full w-[32px] h-[32px] inline-flex justify-center items-center">
|
||||
<ArrowRightIcon />
|
||||
</span>
|
||||
<span className="font-sans text-xs uppercase text-palette-400">View all</span>
|
||||
</Link>
|
||||
);
|
||||
|
||||
export default function LatestActivity() {
|
||||
return (
|
||||
<Panel title="Latest activity">
|
||||
<Tabs>
|
||||
<Tab id="uploads" title="Uploads" />
|
||||
<Tab id="downloads" title="Downloads" />
|
||||
<TabPanel tabId="uploads" className="pt-4">
|
||||
<ActivityTable type="uploads" />
|
||||
<ViewAllLink to="/files?tab=uploads" />
|
||||
</TabPanel>
|
||||
<TabPanel tabId="downloads" className="pt-4">
|
||||
<ActivityTable type="downloads" />
|
||||
<ViewAllLink to="/files?tab=downloads" />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<Panel title="Latest uploads">
|
||||
<ActivityTable type="uploads" />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { Link } from "gatsby";
|
||||
|
||||
import { ArrowRightIcon } from "../Icons";
|
||||
|
||||
export const ViewAllLink = (props) => (
|
||||
<Link className="inline-flex mt-6 items-center gap-3 ease-in-out hover:brightness-90" {...props}>
|
||||
<span className="bg-primary rounded-full w-[32px] h-[32px] inline-flex justify-center items-center">
|
||||
<ArrowRightIcon />
|
||||
</span>
|
||||
<span className="font-sans text-xs uppercase text-palette-400">View all</span>
|
||||
</Link>
|
||||
);
|
|
@ -1,26 +0,0 @@
|
|||
import { useMemo } from "react";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
const parseFileName = (fileName) => {
|
||||
const lastDotIndex = Math.max(0, fileName.lastIndexOf(".")) || Infinity;
|
||||
|
||||
return [fileName.substr(0, lastDotIndex), fileName.substr(lastDotIndex)];
|
||||
};
|
||||
|
||||
const formatItem = ({ size, name: rawFileName, uploadedOn, downloadedOn, ...rest }) => {
|
||||
const [name, type] = parseFileName(rawFileName);
|
||||
const date = dayjs(uploadedOn || downloadedOn).format("MM/DD/YYYY; HH:MM");
|
||||
|
||||
return {
|
||||
...rest,
|
||||
date,
|
||||
size: prettyBytes(size),
|
||||
type,
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
const useFormattedActivityData = (items) => useMemo(() => items.map(formatItem), [items]);
|
||||
|
||||
export default useFormattedActivityData;
|
|
@ -97,7 +97,7 @@ export const NavBar = () => {
|
|||
as="button"
|
||||
onClick={onLogout}
|
||||
activeClassName="text-primary"
|
||||
className="cursor-pointer"
|
||||
className="cursor-pointer w-full"
|
||||
icon={LockClosedIcon}
|
||||
label="Log out"
|
||||
/>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from "./Tooltip";
|
|
@ -118,7 +118,7 @@ const Uploader = ({ mode }) => {
|
|||
</div>
|
||||
|
||||
{uploads.length > 0 && (
|
||||
<div className="flex flex-col space-y-4 py-10">
|
||||
<div className="flex flex-col space-y-4 pt-6 pb-10">
|
||||
{uploads.map((upload) => (
|
||||
<UploaderItem key={upload.id} onUploadStateChange={onUploadStateChange} upload={upload} />
|
||||
))}
|
||||
|
|
|
@ -109,7 +109,6 @@ export default function UploaderItem({ onUploadStateChange, upload }) {
|
|||
{upload.status === "uploading" && (
|
||||
<span className="uppercase tabular-nums">{Math.floor(upload.progress * 100)}%</span>
|
||||
)}
|
||||
{upload.status === "processing" && <span className="uppercase text-palette-300">Wait</span>}
|
||||
{upload.status === "complete" && (
|
||||
<button
|
||||
className="uppercase hover:text-primary transition-colors duration-200"
|
||||
|
|
|
@ -34,8 +34,9 @@ export const AccountRemovalForm = ({ abort, onSuccess }) => {
|
|||
<Form className="flex flex-col gap-4">
|
||||
<div>
|
||||
<h4>Delete account</h4>
|
||||
<p>This will completely delete your account.</p>
|
||||
<p>
|
||||
This will completely delete your account. <strong>This process can't be undone.</strong>
|
||||
<strong>This process cannot be undone.</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import * as Yup from "yup";
|
|||
import { forwardRef, useImperativeHandle, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { Formik, Form } from "formik";
|
||||
import cn from "classnames";
|
||||
|
||||
import accountsService from "../../services/accountsService";
|
||||
|
||||
|
@ -9,7 +10,6 @@ import { Alert } from "../Alert";
|
|||
import { Button } from "../Button";
|
||||
import { CopyButton } from "../CopyButton";
|
||||
import { TextField } from "../Form/TextField";
|
||||
import { CircledProgressIcon, PlusIcon } from "../Icons";
|
||||
|
||||
const newAPIKeySchema = Yup.object().shape({
|
||||
name: Yup.string(),
|
||||
|
@ -22,7 +22,7 @@ const State = {
|
|||
};
|
||||
|
||||
export const APIKeyType = {
|
||||
Public: "public",
|
||||
Sponsor: "sponsor",
|
||||
General: "general",
|
||||
};
|
||||
|
||||
|
@ -37,10 +37,10 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
|||
return (
|
||||
<div ref={ref} className="flex flex-col gap-4">
|
||||
{state === State.Success && (
|
||||
<Alert $variant="success" className="text-center">
|
||||
<Alert $variant="success">
|
||||
<strong>Success!</strong>
|
||||
<p>Please copy your new API key below. We'll never show it again!</p>
|
||||
<div className="flex items-center gap-2 mt-4 justify-center">
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
||||
{generatedKey}
|
||||
</code>
|
||||
|
@ -62,8 +62,8 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
|||
.post("user/apikeys", {
|
||||
json: {
|
||||
name,
|
||||
public: type === APIKeyType.Public ? "true" : "false",
|
||||
skylinks: type === APIKeyType.Public ? [] : null,
|
||||
public: type === APIKeyType.Sponsor ? "true" : "false",
|
||||
skylinks: type === APIKeyType.Sponsor ? [] : null,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
@ -78,26 +78,20 @@ export const AddAPIKeyForm = forwardRef(({ onSuccess, type }, ref) => {
|
|||
}}
|
||||
>
|
||||
{({ errors, touched, isSubmitting }) => (
|
||||
<Form className="grid grid-cols-[1fr_min-content] w-full gap-y-2 gap-x-4 items-start">
|
||||
<div className="flex items-center">
|
||||
<TextField
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
label="New API Key Name"
|
||||
placeholder="my_applications_statistics"
|
||||
error={errors.name}
|
||||
touched={touched.name}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex mt-5 justify-center">
|
||||
{isSubmitting ? (
|
||||
<CircledProgressIcon size={38} className="text-palette-300 animate-[spin_3s_linear_infinite]" />
|
||||
) : (
|
||||
<Button type="submit" className="px-2.5" aria-label="Create general API key">
|
||||
<PlusIcon size={14} />
|
||||
</Button>
|
||||
)}
|
||||
<Form className="flex flex-col gap-4">
|
||||
<TextField
|
||||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
label="New API Key Label"
|
||||
placeholder="my_applications_statistics"
|
||||
error={errors.name}
|
||||
touched={touched.name}
|
||||
/>
|
||||
<div className="flex justify-center">
|
||||
<Button type="submit" disabled={isSubmitting} className={cn({ "cursor-wait": isSubmitting })}>
|
||||
{isSubmitting ? "Generating your API key..." : "Generate your API key"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
|
@ -110,5 +104,5 @@ AddAPIKeyForm.displayName = "AddAPIKeyForm";
|
|||
|
||||
AddAPIKeyForm.propTypes = {
|
||||
onSuccess: PropTypes.func.isRequired,
|
||||
type: PropTypes.oneOf([APIKeyType.Public, APIKeyType.General]).isRequired,
|
||||
type: PropTypes.oneOf([APIKeyType.Sponsor, APIKeyType.General]).isRequired,
|
||||
};
|
||||
|
|
|
@ -19,7 +19,7 @@ const newSkylinkSchema = Yup.object().shape({
|
|||
}),
|
||||
});
|
||||
|
||||
export const AddSkylinkToAPIKeyForm = ({ addSkylink }) => (
|
||||
export const AddSkylinkToSponsorKeyForm = ({ addSkylink }) => (
|
||||
<Formik
|
||||
initialValues={{
|
||||
skylink: "",
|
||||
|
@ -58,6 +58,6 @@ export const AddSkylinkToAPIKeyForm = ({ addSkylink }) => (
|
|||
</Formik>
|
||||
);
|
||||
|
||||
AddSkylinkToAPIKeyForm.propTypes = {
|
||||
AddSkylinkToSponsorKeyForm.propTypes = {
|
||||
addSkylink: PropTypes.func.isRequired,
|
||||
};
|
|
@ -25,7 +25,7 @@ const skylinkValidator = (optional) => (value) => {
|
|||
}
|
||||
};
|
||||
|
||||
const newPublicAPIKeySchema = Yup.object().shape({
|
||||
const newSponsorKeySchema = Yup.object().shape({
|
||||
name: Yup.string(),
|
||||
skylinks: Yup.array().of(Yup.string().test("skylink", "Provide a valid Skylink", skylinkValidator(false))),
|
||||
nextSkylink: Yup.string().when("skylinks", {
|
||||
|
@ -41,7 +41,7 @@ const State = {
|
|||
Failure: "FAILURE",
|
||||
};
|
||||
|
||||
export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||
export const AddSponsorKeyForm = forwardRef(({ onSuccess }, ref) => {
|
||||
const [state, setState] = useState(State.Pure);
|
||||
const [generatedKey, setGeneratedKey] = useState(null);
|
||||
|
||||
|
@ -52,10 +52,10 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
|||
return (
|
||||
<div ref={ref} className="flex flex-col gap-4">
|
||||
{state === State.Success && (
|
||||
<Alert $variant="success" className="text-center">
|
||||
<Alert $variant="success">
|
||||
<strong>Success!</strong>
|
||||
<p>Please copy your new API key below. We'll never show it again!</p>
|
||||
<div className="flex items-center gap-2 mt-4 justify-center">
|
||||
<div className="flex items-center gap-2 mt-4">
|
||||
<code className="p-2 rounded border border-palette-200 text-xs selection:bg-primary/30 truncate">
|
||||
{generatedKey}
|
||||
</code>
|
||||
|
@ -72,7 +72,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
|||
skylinks: [],
|
||||
nextSkylink: "",
|
||||
}}
|
||||
validationSchema={newPublicAPIKeySchema}
|
||||
validationSchema={newSponsorKeySchema}
|
||||
onSubmit={async ({ name, skylinks, nextSkylink }, { resetForm }) => {
|
||||
try {
|
||||
const { key } = await accountsService
|
||||
|
@ -101,14 +101,14 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
|||
type="text"
|
||||
id="name"
|
||||
name="name"
|
||||
label="Public API Key Name"
|
||||
label="Sponsor API Key Name"
|
||||
placeholder="my_applications_statistics"
|
||||
error={errors.name}
|
||||
touched={touched.name}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h6 className="text-palette-300 mb-2">Skylinks accessible with the new key</h6>
|
||||
<h6 className="text-palette-300 mb-2">Skylinks sponsored by the new key</h6>
|
||||
<FieldArray
|
||||
name="skylinks"
|
||||
render={({ push, remove }) => {
|
||||
|
@ -182,7 +182,7 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
|||
className={cn("px-2.5", { "cursor-wait": isSubmitting })}
|
||||
disabled={!isValid || isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Generating" : "Generate"} your public key
|
||||
{isSubmitting ? "Generating your sponsor key..." : "Generate your sponsor key"}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
@ -192,8 +192,8 @@ export const AddPublicAPIKeyForm = forwardRef(({ onSuccess }, ref) => {
|
|||
);
|
||||
});
|
||||
|
||||
AddPublicAPIKeyForm.displayName = "AddAPIKeyForm";
|
||||
AddSponsorKeyForm.displayName = "AddSponsorKeyForm";
|
||||
|
||||
AddPublicAPIKeyForm.propTypes = {
|
||||
AddSponsorKeyForm.propTypes = {
|
||||
onSuccess: PropTypes.func.isRequired,
|
||||
};
|
|
@ -82,7 +82,7 @@ export const LoginForm = ({ onSuccess }) => {
|
|||
</div>
|
||||
|
||||
<p className="text-sm text-center mt-8">
|
||||
Don't have an account? <HighlightedLink to="/auth/signup">Sign up</HighlightedLink>
|
||||
Don't have an account? <HighlightedLink to="/auth/registration">Sign up</HighlightedLink>
|
||||
</p>
|
||||
</Form>
|
||||
)}
|
||||
|
|
|
@ -32,14 +32,16 @@ export const SignUpForm = ({ onSuccess, onFailure }) => (
|
|||
validationSchema={registrationSchema}
|
||||
onSubmit={async ({ email, password }, { setErrors }) => {
|
||||
try {
|
||||
await accountsService.post("user", {
|
||||
json: {
|
||||
email,
|
||||
password,
|
||||
},
|
||||
});
|
||||
const user = await accountsService
|
||||
.post("user", {
|
||||
json: {
|
||||
email,
|
||||
password,
|
||||
},
|
||||
})
|
||||
.json();
|
||||
|
||||
onSuccess();
|
||||
onSuccess(user);
|
||||
} catch (err) {
|
||||
let isFormErrorSet = false;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ const fetcher = async (path) => {
|
|||
};
|
||||
|
||||
export const PortalSettingsProvider = ({ children }) => {
|
||||
const { data, error } = useSWRImmutable("/__internal/do/not/use/accounts", fetcher);
|
||||
const { data, error } = useSWRImmutable("__internal/do/not/use/accounts", fetcher);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [settings, setSettings] = useState(defaultSettings);
|
||||
|
||||
|
|
|
@ -1,17 +1,41 @@
|
|||
import { navigate } from "gatsby";
|
||||
import { useEffect, useState } from "react";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
|
||||
import { UnauthorizedError } from "../../lib/swrConfig";
|
||||
import { FullScreenLoadingIndicator } from "../../components/LoadingIndicator";
|
||||
import { UserContext } from "./UserContext";
|
||||
|
||||
export const UserProvider = ({ children }) => {
|
||||
export const UserProvider = ({ children, allowGuests = false, allowAuthenticated = true }) => {
|
||||
const { data: user, error, mutate } = useSWRImmutable("user");
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (user || error) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [user, error]);
|
||||
const guard = async () => {
|
||||
if (user) {
|
||||
if (!allowAuthenticated) {
|
||||
navigate("/");
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
} else if (error) {
|
||||
if (error instanceof UnauthorizedError && !allowGuests) {
|
||||
await navigate(`/auth/login?return_to=${encodeURIComponent(window.location.href)}`);
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
} else if (user === null) {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return <UserContext.Provider value={{ user, error, loading, mutate }}>{children}</UserContext.Provider>;
|
||||
guard();
|
||||
}, [user, error, allowGuests, allowAuthenticated]);
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={{ user, error, loading, mutate }}>
|
||||
{loading && <FullScreenLoadingIndicator />}
|
||||
{!loading && children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { SWRConfig } from "swr";
|
||||
|
||||
import { UserProvider } from "../contexts/user";
|
||||
import { guestsOnly, allUsers } from "../lib/swrConfig";
|
||||
|
||||
const Layout = styled.div.attrs({
|
||||
className: "min-h-screen w-screen bg-black flex",
|
||||
|
@ -22,29 +20,39 @@ const Content = styled.div.attrs({
|
|||
})``;
|
||||
|
||||
const AuthLayout =
|
||||
(swrConfig) =>
|
||||
({ children }) => {
|
||||
return (
|
||||
(userProviderProps) =>
|
||||
({ children }) =>
|
||||
(
|
||||
<>
|
||||
<SWRConfig value={swrConfig}>
|
||||
<UserProvider>
|
||||
<Layout>
|
||||
<SloganContainer className="pl-20 pr-20 lg:pr-30 xl:pr-40">
|
||||
<div className="">
|
||||
<h1 className="text-4xl lg:text-5xl xl:text-6xl text-white">
|
||||
The decentralized <span className="text-primary">revolution</span> starts with decentralized storage
|
||||
</h1>
|
||||
<UserProvider {...userProviderProps}>
|
||||
<Layout>
|
||||
<SloganContainer className="pl-20 pr-20 lg:pr-30 xl:pr-40">
|
||||
<div className="">
|
||||
<h1 className="text-4xl lg:text-5xl xl:text-6xl text-white">
|
||||
The decentralized <span className="text-primary">revolution</span> starts with decentralized storage
|
||||
</h1>
|
||||
</div>
|
||||
</SloganContainer>
|
||||
<Content>
|
||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||
<div className="mb-4 md:mb-16">
|
||||
<img src="/images/logo-black-text.svg" alt="Skynet" className="-ml-2" />
|
||||
</div>
|
||||
</SloganContainer>
|
||||
<Content>{children}</Content>
|
||||
</Layout>
|
||||
</UserProvider>
|
||||
</SWRConfig>
|
||||
{children}
|
||||
</div>
|
||||
</Content>
|
||||
</Layout>
|
||||
</UserProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Some pages (e.g. email confirmation) need to be accessible to both logged-in and guest users.
|
||||
export const AllUsersAuthLayout = AuthLayout(allUsers);
|
||||
export const AllUsersAuthLayout = AuthLayout({
|
||||
allowGuests: true,
|
||||
allowAuthenticated: true,
|
||||
});
|
||||
|
||||
export default AuthLayout(guestsOnly);
|
||||
export default AuthLayout({
|
||||
allowGuests: true,
|
||||
allowAuthenticated: false,
|
||||
});
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { SWRConfig } from "swr";
|
||||
|
||||
import { authenticatedOnly } from "../lib/swrConfig";
|
||||
|
||||
import { PageContainer } from "../components/PageContainer";
|
||||
import { NavBar } from "../components/NavBar";
|
||||
|
@ -30,22 +27,16 @@ const Layout = ({ children }) => {
|
|||
);
|
||||
};
|
||||
|
||||
const DashboardLayout = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<SWRConfig value={authenticatedOnly}>
|
||||
<UserProvider>
|
||||
<Layout>
|
||||
<NavBar />
|
||||
<PageContainer>
|
||||
<main className="mt-14">{children}</main>
|
||||
</PageContainer>
|
||||
<Footer />
|
||||
</Layout>
|
||||
</UserProvider>
|
||||
</SWRConfig>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const DashboardLayout = ({ children }) => (
|
||||
<>
|
||||
<UserProvider>
|
||||
<Layout>
|
||||
<NavBar />
|
||||
<PageContainer className="mt-2 md:mt-14">{children}</PageContainer>
|
||||
<Footer />
|
||||
</Layout>
|
||||
</UserProvider>
|
||||
</>
|
||||
);
|
||||
|
||||
export default DashboardLayout;
|
||||
|
|
|
@ -1,43 +1,12 @@
|
|||
import * as React from "react";
|
||||
import { Link } from "gatsby";
|
||||
import styled from "styled-components";
|
||||
import { SWRConfig } from "swr";
|
||||
|
||||
import { authenticatedOnly } from "../lib/swrConfig";
|
||||
|
||||
import { PageContainer } from "../components/PageContainer";
|
||||
import { NavBar } from "../components/NavBar";
|
||||
import { Footer } from "../components/Footer";
|
||||
import { UserProvider, useUser } from "../contexts/user";
|
||||
import { ContainerLoadingIndicator } from "../components/LoadingIndicator";
|
||||
|
||||
const Wrapper = styled.div.attrs({
|
||||
className: "min-h-screen overflow-hidden",
|
||||
})`
|
||||
background-image: url(/images/dashboard-bg.svg);
|
||||
background-position: center -280px;
|
||||
background-repeat: no-repeat;
|
||||
`;
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
const { user } = useUser();
|
||||
|
||||
// Prevent from flashing the dashboard screen to unauthenticated users.
|
||||
return (
|
||||
<Wrapper>
|
||||
{!user && (
|
||||
<div className="fixed inset-0 flex justify-center items-center bg-palette-100/50">
|
||||
<ContainerLoadingIndicator className="!text-palette-200/50" />
|
||||
</div>
|
||||
)}
|
||||
{user && <>{children}</>}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
import DashboardLayout from "./DashboardLayout";
|
||||
|
||||
const Sidebar = () => (
|
||||
<aside className="w-full lg:w-48 bg-white text-sm font-sans font-light text-palette-600 shrink-0">
|
||||
<nav>
|
||||
<aside className="w-full lg:w-48 text-sm font-sans font-light text-palette-600 shrink-0">
|
||||
<nav className="bg-white">
|
||||
<SidebarLink activeClassName="!border-l-primary" to="/settings">
|
||||
Account
|
||||
</SidebarLink>
|
||||
|
@ -47,15 +16,15 @@ const Sidebar = () => (
|
|||
<SidebarLink activeClassName="!border-l-primary" to="/settings/export">
|
||||
Export
|
||||
</SidebarLink>
|
||||
<SidebarLink activeClassName="!border-l-primary" to="/settings/api-keys">
|
||||
API Keys
|
||||
<SidebarLink activeClassName="!border-l-primary" to="/settings/developer-settings">
|
||||
Developer settings
|
||||
</SidebarLink>
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
|
||||
const SidebarLink = styled(Link).attrs({
|
||||
className: `h-12 py-3 px-6 h-full w-full flex
|
||||
className: `h-12 py-3 px-6 w-full flex
|
||||
border-l-2 border-l-palette-200
|
||||
border-b border-b-palette-100 last:border-b-transparent`,
|
||||
})``;
|
||||
|
@ -67,21 +36,13 @@ const Content = styled.main.attrs({
|
|||
`;
|
||||
|
||||
const UserSettingsLayout = ({ children }) => (
|
||||
<SWRConfig value={authenticatedOnly}>
|
||||
<UserProvider>
|
||||
<Layout>
|
||||
<NavBar />
|
||||
<PageContainer className="mt-2 md:mt-14">
|
||||
<h6 className="hidden md:block mb-2 text-palette-400">Settings</h6>
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<Sidebar />
|
||||
<Content className="lg:w-settings-lg xl:w-settings-xl">{children}</Content>
|
||||
</div>
|
||||
</PageContainer>
|
||||
<Footer />
|
||||
</Layout>
|
||||
</UserProvider>
|
||||
</SWRConfig>
|
||||
<DashboardLayout>
|
||||
<h6 className="hidden md:block mb-2 text-palette-400">Settings</h6>
|
||||
<div className="flex flex-col lg:flex-row">
|
||||
<Sidebar />
|
||||
<Content className="lg:w-settings-lg xl:w-settings-xl">{children}</Content>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
|
||||
export default UserSettingsLayout;
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export const DATE_FORMAT = "MMM D, YYYY HH:MM";
|
|
@ -1,39 +1,22 @@
|
|||
import { navigate } from "gatsby";
|
||||
import { StatusCodes } from "http-status-codes";
|
||||
|
||||
// TODO: portal-aware URL
|
||||
const baseUrl = process.env.NODE_ENV !== "production" ? "/api" : "https://account.skynetpro.net/api";
|
||||
export class UnauthorizedError extends Error {}
|
||||
|
||||
const redirectUnauthenticated = (key) =>
|
||||
fetch(`${baseUrl}/${key}`).then((response) => {
|
||||
if (response.status === StatusCodes.UNAUTHORIZED) {
|
||||
navigate(`/auth/login?return_to=${encodeURIComponent(window.location.href)}`);
|
||||
return null;
|
||||
}
|
||||
const config = {
|
||||
fetcher: (key) =>
|
||||
fetch(`/api/${key}`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
return response.json();
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
const redirectAuthenticated = (key) =>
|
||||
fetch(`${baseUrl}/${key}`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
await navigate("/");
|
||||
return response.json();
|
||||
}
|
||||
if (response.status === StatusCodes.UNAUTHORIZED) {
|
||||
throw new UnauthorizedError(data?.message || "Unauthorized");
|
||||
}
|
||||
|
||||
// If there was an error, let's throw so useSWR's "error" property is populated instead "data".
|
||||
const data = await response.json();
|
||||
throw new Error(data?.message || `Error occured when trying to fetch: ${key}`);
|
||||
});
|
||||
|
||||
export const allUsers = {
|
||||
fetcher: (key) => fetch(`${baseUrl}/${key}`).then((response) => response.json()),
|
||||
throw new Error(data?.message || `Error occurred when trying to fetch: ${key}`);
|
||||
}),
|
||||
};
|
||||
|
||||
export const authenticatedOnly = {
|
||||
fetcher: redirectUnauthenticated,
|
||||
};
|
||||
|
||||
export const guestsOnly = {
|
||||
fetcher: redirectAuthenticated,
|
||||
};
|
||||
export default config;
|
||||
|
|
|
@ -22,16 +22,11 @@ const LoginPage = ({ location }) => {
|
|||
<Metadata>
|
||||
<title>Sign In</title>
|
||||
</Metadata>
|
||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||
<div className="mb-4 md:mb-16">
|
||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||
</div>
|
||||
<LoginForm
|
||||
onSuccess={async () => {
|
||||
await refreshUserState();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<LoginForm
|
||||
onSuccess={async () => {
|
||||
await refreshUserState();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { navigate } from "gatsby";
|
||||
import { useCallback, useState } from "react";
|
||||
import bytes from "pretty-bytes";
|
||||
|
||||
import AuthLayout from "../../layouts/AuthLayout";
|
||||
|
@ -10,6 +9,7 @@ import { SignUpForm } from "../../components/forms/SignUpForm";
|
|||
import { usePortalSettings } from "../../contexts/portal-settings";
|
||||
import { PlansProvider, usePlans } from "../../contexts/plans";
|
||||
import { Metadata } from "../../components/Metadata";
|
||||
import { useUser } from "../../contexts/user";
|
||||
|
||||
const FreePortalHeader = () => {
|
||||
const { plans } = usePlans();
|
||||
|
@ -47,25 +47,21 @@ const State = {
|
|||
const SignUpPage = () => {
|
||||
const [state, setState] = useState(State.Pure);
|
||||
const { settings } = usePortalSettings();
|
||||
const { mutate: refreshUserState } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (state === State.Success) {
|
||||
const timer = setTimeout(() => navigate(settings.isSubscriptionRequired ? "/upgrade" : "/"), 3000);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [state, settings.isSubscriptionRequired]);
|
||||
const onUserCreated = useCallback(
|
||||
(newUser) => {
|
||||
refreshUserState(newUser);
|
||||
},
|
||||
[refreshUserState]
|
||||
);
|
||||
|
||||
return (
|
||||
<PlansProvider>
|
||||
<Metadata>
|
||||
<title>Sign Up</title>
|
||||
</Metadata>
|
||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||
<div className="mb-4 md:mb-16">
|
||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
{!settings.areAccountsEnabled && <Alert $variant="info">Accounts are not enabled on this portal.</Alert>}
|
||||
|
||||
{settings.areAccountsEnabled && (
|
||||
|
@ -73,15 +69,13 @@ const SignUpPage = () => {
|
|||
{settings.isSubscriptionRequired ? <PaidPortalHeader /> : <FreePortalHeader />}
|
||||
|
||||
{state !== State.Success && (
|
||||
<SignUpForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
||||
)}
|
||||
<>
|
||||
<SignUpForm onSuccess={onUserCreated} onFailure={() => setState(State.Failure)} />
|
||||
|
||||
{state === State.Success && (
|
||||
<div className="text-center">
|
||||
<p className="text-primary font-semibold">Please check your inbox and confirm your email address.</p>
|
||||
<p>You will be redirected to your dashboard shortly.</p>
|
||||
<HighlightedLink to="/">Click here to go there now.</HighlightedLink>
|
||||
</div>
|
||||
<p className="text-sm text-center mt-8">
|
||||
Already have an account? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
{state === State.Failure && (
|
||||
|
@ -89,10 +83,6 @@ const SignUpPage = () => {
|
|||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<p className="text-sm text-center mt-8">
|
||||
Already have an account? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||
</p>
|
||||
</div>
|
||||
</PlansProvider>
|
||||
);
|
|
@ -20,30 +20,25 @@ const ResetPasswordPage = () => {
|
|||
<Metadata>
|
||||
<title>Reset Password</title>
|
||||
</Metadata>
|
||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||
<div className="mb-4 md:mb-16">
|
||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||
</div>
|
||||
{state !== State.Success && (
|
||||
<RecoveryForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
||||
)}
|
||||
{state !== State.Success && (
|
||||
<RecoveryForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
||||
)}
|
||||
|
||||
{state === State.Success && (
|
||||
<p className="text-primary text-center font-semibold">Please check your inbox for further instructions.</p>
|
||||
)}
|
||||
{state === State.Success && (
|
||||
<p className="text-primary text-center font-semibold">Please check your inbox for further instructions.</p>
|
||||
)}
|
||||
|
||||
{state === State.Failure && (
|
||||
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
||||
)}
|
||||
{state === State.Failure && (
|
||||
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
||||
)}
|
||||
|
||||
<div className="text-sm text-center mt-8">
|
||||
<p>
|
||||
Suddenly remembered your password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||
</p>
|
||||
<p>
|
||||
Don't actually have an account? <HighlightedLink to="/auth/signup">Create one!</HighlightedLink>
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-sm text-center mt-8">
|
||||
<p>
|
||||
Suddenly remembered your password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||
</p>
|
||||
<p>
|
||||
Don't actually have an account? <HighlightedLink to="/auth/registration">Create one!</HighlightedLink>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,32 +1,19 @@
|
|||
import * as React from "react";
|
||||
import { useSearchParam } from "react-use";
|
||||
|
||||
import DashboardLayout from "../layouts/DashboardLayout";
|
||||
|
||||
import { Panel } from "../components/Panel";
|
||||
import { Tab, TabPanel, Tabs } from "../components/Tabs";
|
||||
import { Metadata } from "../components/Metadata";
|
||||
import FileList from "../components/FileList/FileList";
|
||||
|
||||
const FilesPage = () => {
|
||||
const defaultTab = useSearchParam("tab");
|
||||
|
||||
return (
|
||||
<>
|
||||
<Metadata>
|
||||
<title>My Files</title>
|
||||
<title>Files</title>
|
||||
</Metadata>
|
||||
<Panel title="Files">
|
||||
<Tabs defaultTab={defaultTab || "uploads"}>
|
||||
<Tab id="uploads" title="Uploads" />
|
||||
<Tab id="downloads" title="Downloads" />
|
||||
<TabPanel tabId="uploads" className="pt-4">
|
||||
<FileList type="uploads" />
|
||||
</TabPanel>
|
||||
<TabPanel tabId="downloads" className="pt-4">
|
||||
<FileList type="downloads" />
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
<FileList type="uploads" />
|
||||
</Panel>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -6,13 +6,14 @@ import UserSettingsLayout from "../../layouts/UserSettingsLayout";
|
|||
import { AddAPIKeyForm, APIKeyType } from "../../components/forms/AddAPIKeyForm";
|
||||
import { APIKeyList } from "../../components/APIKeyList/APIKeyList";
|
||||
import { Alert } from "../../components/Alert";
|
||||
import { AddPublicAPIKeyForm } from "../../components/forms/AddPublicAPIKeyForm";
|
||||
import { AddSponsorKeyForm } from "../../components/forms/AddSponsorKeyForm";
|
||||
import { Metadata } from "../../components/Metadata";
|
||||
import HighlightedLink from "../../components/HighlightedLink";
|
||||
|
||||
const APIKeysPage = () => {
|
||||
const { data: apiKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
||||
const generalKeys = apiKeys.filter(({ public: isPublic }) => isPublic === "false");
|
||||
const publicKeys = apiKeys.filter(({ public: isPublic }) => isPublic === "true");
|
||||
const DeveloperSettingsPage = () => {
|
||||
const { data: allKeys = [], mutate: reloadKeys, error } = useSWR("user/apikeys");
|
||||
const apiKeys = allKeys.filter(({ public: isPublic }) => isPublic === "false");
|
||||
const sponsorKeys = allKeys.filter(({ public: isPublic }) => isPublic === "true");
|
||||
|
||||
const publicFormRef = useRef();
|
||||
const generalFormRef = useRef();
|
||||
|
@ -31,53 +32,57 @@ const APIKeysPage = () => {
|
|||
return (
|
||||
<>
|
||||
<Metadata>
|
||||
<title>API Keys</title>
|
||||
<title>Developer settings</title>
|
||||
</Metadata>
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px] leading-relaxed">
|
||||
<div>
|
||||
<h4>API Keys</h4>
|
||||
<p className="leading-relaxed">There are two types of API keys that you can generate for your account.</p>
|
||||
<p>Make sure to use the appropriate type.</p>
|
||||
<h4>Developer settings</h4>
|
||||
<p>API keys allow developers and applications to extend the functionality of your portal account.</p>
|
||||
<p>Skynet uses two types of API keys, explained below.</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h5>Public keys</h5>
|
||||
<p className="text-palette-500">
|
||||
Public keys provide read access to a selected list of skylinks. You can share them publicly.
|
||||
<h5>Sponsor keys</h5>
|
||||
<div className="text-palette-500"></div>
|
||||
<p>
|
||||
Sponsor keys allow users without an account on this portal to download skylinks covered by the API key.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Learn more about sponsoring content with Sponsor API Keys{" "}
|
||||
<HighlightedLink as="a" href="#">
|
||||
here
|
||||
</HighlightedLink>
|
||||
.
|
||||
</p>{" "}
|
||||
{/* TODO: missing documentation link */}
|
||||
<div className="mt-4">
|
||||
<AddPublicAPIKeyForm ref={publicFormRef} onSuccess={refreshState} />
|
||||
<AddSponsorKeyForm ref={publicFormRef} onSuccess={refreshState} />
|
||||
</div>
|
||||
|
||||
{error ? (
|
||||
<Alert $variant="error" className="mt-4">
|
||||
An error occurred while loading your API keys. Please try again later.
|
||||
An error occurred while loading your sponsor keys. Please try again later.
|
||||
</Alert>
|
||||
) : (
|
||||
<div className="mt-4">
|
||||
{publicKeys?.length > 0 ? (
|
||||
<APIKeyList title="Your public keys" keys={publicKeys} reloadKeys={() => refreshState(true)} />
|
||||
{sponsorKeys?.length > 0 ? (
|
||||
<APIKeyList title="Your public keys" keys={sponsorKeys} reloadKeys={() => refreshState(true)} />
|
||||
) : (
|
||||
<Alert $variant="info">No public API keys found.</Alert>
|
||||
<Alert $variant="info">No sponsor keys found.</Alert>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<section className="flex flex-col gap-2">
|
||||
<h5>General keys</h5>
|
||||
<h5>API keys</h5>
|
||||
<p className="text-palette-500">
|
||||
These keys provide full access to <b>Accounts</b> service and are equivalent to using a JWT token.
|
||||
These keys allow uploading and downloading skyfiles, as well as reading and writing to the registry.
|
||||
</p>
|
||||
<p className="underline">
|
||||
This type of API keys needs to be kept secret and should never be shared with anyone.
|
||||
</p>
|
||||
|
||||
<div className="mt-4">
|
||||
<AddAPIKeyForm ref={generalFormRef} onSuccess={refreshState} type={APIKeyType.General} />
|
||||
</div>
|
||||
|
@ -88,10 +93,10 @@ const APIKeysPage = () => {
|
|||
</Alert>
|
||||
) : (
|
||||
<div className="mt-4">
|
||||
{generalKeys?.length > 0 ? (
|
||||
<APIKeyList title="Your general keys" keys={generalKeys} reloadKeys={() => refreshState(true)} />
|
||||
{apiKeys?.length > 0 ? (
|
||||
<APIKeyList title="Your API keys" keys={apiKeys} reloadKeys={() => refreshState(true)} />
|
||||
) : (
|
||||
<Alert $variant="info">No general API keys found.</Alert>
|
||||
<Alert $variant="info">No API keys found.</Alert>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
@ -105,6 +110,6 @@ const APIKeysPage = () => {
|
|||
);
|
||||
};
|
||||
|
||||
APIKeysPage.Layout = UserSettingsLayout;
|
||||
DeveloperSettingsPage.Layout = UserSettingsLayout;
|
||||
|
||||
export default APIKeysPage;
|
||||
export default DeveloperSettingsPage;
|
|
@ -38,8 +38,8 @@ const ExportPage = () => {
|
|||
<section>
|
||||
<h4>Export</h4>
|
||||
<p>
|
||||
Et quidem exercitus quid ex eo delectu rerum, quem modo ista sis aequitate. Probabo, inquit, modo dixi,
|
||||
constituto.
|
||||
Select the items you want to export. You can use this data to migrate your account to another Skynet
|
||||
portal.
|
||||
</p>
|
||||
</section>
|
||||
<hr />
|
||||
|
|
|
@ -8,6 +8,10 @@ import { Modal } from "../../components/Modal/Modal";
|
|||
import { AccountRemovalForm } from "../../components/forms/AccountRemovalForm";
|
||||
import { Alert } from "../../components/Alert";
|
||||
import { Metadata } from "../../components/Metadata";
|
||||
import HighlightedLink from "../../components/HighlightedLink";
|
||||
import { AvatarUploader } from "../../components/AvatarUploader/AvatarUploader";
|
||||
import { useMedia } from "react-use";
|
||||
import theme from "../../lib/theme";
|
||||
|
||||
const State = {
|
||||
Pure: "PURE",
|
||||
|
@ -19,10 +23,16 @@ const AccountPage = () => {
|
|||
const { user, mutate: reloadUser } = useUser();
|
||||
const [state, setState] = useState(State.Pure);
|
||||
const [removalInitiated, setRemovalInitiated] = useState(false);
|
||||
const isLargeScreen = useMedia(`(min-width: ${theme.screens.xl})`);
|
||||
|
||||
const prompt = () => setRemovalInitiated(true);
|
||||
const abort = () => setRemovalInitiated(false);
|
||||
|
||||
const onAccountRemoved = useCallback(async () => {
|
||||
await reloadUser(null);
|
||||
await navigate("/auth/login");
|
||||
}, [reloadUser]);
|
||||
|
||||
const onSettingsUpdated = useCallback(
|
||||
async (updatedState) => {
|
||||
try {
|
||||
|
@ -45,14 +55,7 @@ const AccountPage = () => {
|
|||
</Metadata>
|
||||
<div className="flex flex-col xl:flex-row">
|
||||
<div className="flex flex-col gap-10 lg:shrink-0 lg:max-w-[576px] xl:max-w-[524px]">
|
||||
<section>
|
||||
<h4>Account</h4>
|
||||
<p>
|
||||
Tum dicere exorsus est laborum et quasi involuta aperiri, altera prompta et expedita. Primum igitur,
|
||||
inquit, modo ista sis aequitate.
|
||||
</p>
|
||||
</section>
|
||||
<hr />
|
||||
<h4>Account</h4>
|
||||
<section className="flex flex-col gap-8">
|
||||
{state === State.Failure && (
|
||||
<Alert $variant="error">There was an error processing your request. Please try again later.</Alert>
|
||||
|
@ -63,7 +66,23 @@ const AccountPage = () => {
|
|||
<hr />
|
||||
<section>
|
||||
<h6 className="text-palette-400">Delete account</h6>
|
||||
<p>This will completely delete your account. This process can't be undone.</p>
|
||||
<div className="my-4">
|
||||
<p>
|
||||
This action will delete your account and <strong>cannot be undone</strong>.
|
||||
</p>
|
||||
<p>
|
||||
Your uploaded files will remain accessible while any portal continues to{" "}
|
||||
<HighlightedLink
|
||||
as="a"
|
||||
href="https://support.skynetlabs.com/key-concepts/faqs#what-is-pinning"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
pin
|
||||
</HighlightedLink>{" "}
|
||||
them to Skynet.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={prompt}
|
||||
|
@ -73,9 +92,12 @@ const AccountPage = () => {
|
|||
</button>
|
||||
</section>
|
||||
</div>
|
||||
<div className="flex w-full justify-start xl:justify-end">
|
||||
{isLargeScreen && <AvatarUploader className="flex flex-col gap-4" />}
|
||||
</div>
|
||||
{removalInitiated && (
|
||||
<Modal onClose={abort} className="text-center">
|
||||
<AccountRemovalForm abort={abort} onSuccess={() => navigate("/auth/login")} />
|
||||
<AccountRemovalForm abort={abort} onSuccess={onAccountRemoved} />
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -18,18 +18,13 @@ const NotificationsPage = () => {
|
|||
<section>
|
||||
{/* TODO: saves on change */}
|
||||
<Switch onChange={console.info.bind(console)} labelClassName="!items-start flex-col md:flex-row">
|
||||
I agreee to get the latest news, updates and special offers delivered to my email inbox.
|
||||
I agree to receive emails of the latest news, updates and offers.
|
||||
</Switch>
|
||||
</section>
|
||||
<hr />
|
||||
<section>
|
||||
<h6 className="text-palette-300">Statistics</h6>
|
||||
{/* TODO: proper content :) */}
|
||||
<p>
|
||||
Si sine causa, nollem me tamen laudandis maioribus meis corrupisti nec in malis. Si sine causa, mox
|
||||
videro.
|
||||
</p>
|
||||
|
||||
<p>Check below to be notified by email when your usage approaches your plan's limits.</p>
|
||||
<ul className="mt-7 flex flex-col gap-2">
|
||||
<li>
|
||||
{/* TODO: saves on change */}
|
||||
|
@ -37,7 +32,7 @@ const NotificationsPage = () => {
|
|||
</li>
|
||||
<li>
|
||||
{/* TODO: saves on change */}
|
||||
<Switch onChange={console.info.bind(console)}>File limit</Switch>
|
||||
<Switch onChange={console.info.bind(console)}>Files limit</Switch>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
|
|
@ -57,23 +57,18 @@ const EmailConfirmationPage = ({ location }) => {
|
|||
<Metadata>
|
||||
<title>Confirm E-mail Address</title>
|
||||
</Metadata>
|
||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||
<div className="mb-4 md:mb-16">
|
||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
{state === State.Pure && <p>Please wait while we verify your account...</p>}
|
||||
<div className="text-center">
|
||||
{state === State.Pure && <p>Please wait while we verify your account...</p>}
|
||||
|
||||
{state === State.Success && (
|
||||
<>
|
||||
<p className="text-primary font-semibold">All done!</p>
|
||||
<p>You will be redirected to your dashboard shortly.</p>
|
||||
<HighlightedLink to="/">Redirect now.</HighlightedLink>
|
||||
</>
|
||||
)}
|
||||
{state === State.Success && (
|
||||
<>
|
||||
<p className="text-primary font-semibold">All done!</p>
|
||||
<p>You will be redirected to your dashboard shortly.</p>
|
||||
<HighlightedLink to="/">Redirect now.</HighlightedLink>
|
||||
</>
|
||||
)}
|
||||
|
||||
{state === State.Failure && <p className="text-error">Something went wrong, please try again later.</p>}
|
||||
</div>
|
||||
{state === State.Failure && <p className="text-error">Something went wrong, please try again later.</p>}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -24,35 +24,30 @@ const RecoverPage = ({ location }) => {
|
|||
<Metadata>
|
||||
<title>Recover Your Account</title>
|
||||
</Metadata>
|
||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||
<div className="mb-4 md:mb-16">
|
||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
||||
</div>
|
||||
{state !== State.Success && (
|
||||
<ResetPasswordForm
|
||||
token={token}
|
||||
onSuccess={() => {
|
||||
setState(State.Success);
|
||||
navigate("/");
|
||||
}}
|
||||
onFailure={() => setState(State.Failure)}
|
||||
/>
|
||||
)}
|
||||
{state !== State.Success && (
|
||||
<ResetPasswordForm
|
||||
token={token}
|
||||
onSuccess={() => {
|
||||
setState(State.Success);
|
||||
navigate("/");
|
||||
}}
|
||||
onFailure={() => setState(State.Failure)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{state === State.Success && (
|
||||
<p className="text-primary text-center font-semibold">
|
||||
All done! You will be redirected to your dashboard shortly.
|
||||
</p>
|
||||
)}
|
||||
|
||||
{state === State.Failure && (
|
||||
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
||||
)}
|
||||
|
||||
<p className="text-sm text-center mt-8">
|
||||
Suddenly remembered your old password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||
{state === State.Success && (
|
||||
<p className="text-primary text-center font-semibold">
|
||||
All done! You will be redirected to your dashboard shortly.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{state === State.Failure && (
|
||||
<p className="text-error text-center">Something went wrong, please try again later.</p>
|
||||
)}
|
||||
|
||||
<p className="text-sm text-center mt-8">
|
||||
Suddenly remembered your old password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { SkynetClient } from "skynet-js";
|
||||
|
||||
export default new SkynetClient("https://skynetpro.net"); // TODO: proper API url
|
||||
export default new SkynetClient(`https://${process.env.GATSBY_PORTAL_DOMAIN}`);
|
||||
|
|
|
@ -6735,11 +6735,31 @@ dot-prop@^5.2.0:
|
|||
dependencies:
|
||||
is-obj "^2.0.0"
|
||||
|
||||
dotenv-cli@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-5.1.0.tgz#0d2942b089082da0157f9b26bd6c5c4dd51ef48e"
|
||||
integrity sha512-NoEZAlKo9WVrG0b3i9mBxdD6INdDuGqdgR74t68t8084QcI077/1MnPerRW1odl+9uULhcdnQp2U0pYVppKHOA==
|
||||
dependencies:
|
||||
cross-spawn "^7.0.3"
|
||||
dotenv "^16.0.0"
|
||||
dotenv-expand "^8.0.1"
|
||||
minimist "^1.2.5"
|
||||
|
||||
dotenv-expand@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
|
||||
integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
|
||||
|
||||
dotenv-expand@^8.0.1:
|
||||
version "8.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e"
|
||||
integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==
|
||||
|
||||
dotenv@^16.0.0:
|
||||
version "16.0.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411"
|
||||
integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==
|
||||
|
||||
dotenv@^8.0.0, dotenv@^8.6.0:
|
||||
version "8.6.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
|
||||
|
@ -11242,9 +11262,9 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
|||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||
|
||||
moment@^2.29.1:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
version "2.29.2"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
|
||||
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
|
||||
|
||||
move-concurrently@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"dependencies": {
|
||||
"@fontsource/sora": "4.5.5",
|
||||
"@fontsource/source-sans-pro": "4.5.6",
|
||||
"@stripe/react-stripe-js": "1.7.0",
|
||||
"@stripe/stripe-js": "1.26.0",
|
||||
"@stripe/react-stripe-js": "1.7.1",
|
||||
"@stripe/stripe-js": "1.27.0",
|
||||
"classnames": "2.3.1",
|
||||
"copy-text-to-clipboard": "^3.0.1",
|
||||
"dayjs": "1.11.0",
|
||||
|
@ -27,15 +27,15 @@
|
|||
"react-dom": "17.0.2",
|
||||
"react-toastify": "8.2.0",
|
||||
"skynet-js": "3.0.2",
|
||||
"stripe": "8.215.0",
|
||||
"swr": "1.2.2",
|
||||
"stripe": "8.216.0",
|
||||
"swr": "1.3.0",
|
||||
"yup": "0.32.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "0.5.0",
|
||||
"@tailwindcss/typography": "0.5.2",
|
||||
"autoprefixer": "10.4.4",
|
||||
"eslint": "8.12.0",
|
||||
"eslint": "8.13.0",
|
||||
"eslint-config-next": "12.1.4",
|
||||
"postcss": "8.4.12",
|
||||
"prettier": "2.6.2",
|
||||
|
|
|
@ -3,7 +3,14 @@ import { useFormik, getIn, setIn } from "formik";
|
|||
import classnames from "classnames";
|
||||
import SelfServiceMessages from "./SelfServiceMessages";
|
||||
|
||||
export default function SelfServiceForm({ fieldsConfig, onSubmit, title, validationSchema = null, button = "Submit" }) {
|
||||
export default function SelfServiceForm({
|
||||
fieldsConfig,
|
||||
onSubmit,
|
||||
title,
|
||||
onError,
|
||||
validationSchema = null,
|
||||
button = "Submit",
|
||||
}) {
|
||||
const [messages, setMessages] = React.useState([]);
|
||||
const fields = fieldsConfig.sort((a, b) => (a.position < b.position ? -1 : 1));
|
||||
const formik = useFormik({
|
||||
|
@ -21,6 +28,9 @@ export default function SelfServiceForm({ fieldsConfig, onSubmit, title, validat
|
|||
const data = await error.response.json();
|
||||
|
||||
if (data.message) {
|
||||
if (typeof onError === "function") {
|
||||
onError(data.message);
|
||||
}
|
||||
setMessages((messages) => [...messages, { type: "error", text: data.message }]);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -23,6 +23,8 @@ export default function Recovery() {
|
|||
useAnonRoute(); // ensure user is not logged in
|
||||
|
||||
const [success, setSuccess] = React.useState(false);
|
||||
const [skynetFreeInviteVisible, setSkynetFreeInviteVisible] = React.useState(false);
|
||||
const isSiaskyNet = typeof window !== "undefined" && window.location.hostname === "account.siasky.net";
|
||||
|
||||
const onSubmit = async (values) => {
|
||||
await accountsApi.post("user/recover/request", {
|
||||
|
@ -64,6 +66,16 @@ export default function Recovery() {
|
|||
</Link>{" "}
|
||||
for a new account
|
||||
</p>
|
||||
{skynetFreeInviteVisible && isSiaskyNet && (
|
||||
<div className="font-content rounded border border-blue-200 mt-6 p-4 bg-blue-100">
|
||||
<p>
|
||||
All Siasky.net accounts have been moved to{" "}
|
||||
<a className="text-primary" href="https://skynetfree.net">
|
||||
SkynetFree.net
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!success && (
|
||||
|
@ -72,6 +84,9 @@ export default function Recovery() {
|
|||
validationSchema={validationSchema}
|
||||
onSubmit={onSubmit}
|
||||
button="Send recovery link"
|
||||
onError={(errorMessage) =>
|
||||
setSkynetFreeInviteVisible(errorMessage === "registrations are currently disabled")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
@ -175,17 +175,17 @@
|
|||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64"
|
||||
integrity sha512-ZK5v4bJwgXldAUA8r3q9YKfCwOqoHTK/ZqRjSeRXQrBXWouoPnS4MQtgC4AXGiiBuUu5wxrRgTlv0ktmM4P1Aw==
|
||||
|
||||
"@stripe/react-stripe-js@1.7.0":
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.0.tgz#83c993a09a903703205d556617f9729784a896c3"
|
||||
integrity sha512-L20v8Jq0TDZFL2+y+uXD751t6q9SalSFkSYZpmZ2VWrGZGK7HAGfRQ804dzYSSr5fGenW6iz6y7U0YKfC/TK3g==
|
||||
"@stripe/react-stripe-js@1.7.1":
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.1.tgz#6e1db8f4a0eaf2193b153173d4aa7c38b681310d"
|
||||
integrity sha512-GiUPoMo0xVvmpRD6JR9JAhAZ0W3ZpnYZNi0KE+91+tzrSFVpChKZbeSsJ5InlZhHFk9NckJCt1wOYBTqNsvt3A==
|
||||
dependencies:
|
||||
prop-types "^15.7.2"
|
||||
|
||||
"@stripe/stripe-js@1.26.0":
|
||||
version "1.26.0"
|
||||
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.26.0.tgz#45670924753c01e18d0544ea1f1067b474aaa96f"
|
||||
integrity sha512-4R1vC75yKaCVFARW3bhelf9+dKt4NP4iZY/sIjGK7AAMBVvZ47eG74NvsAIUdUnhOXSWFMjdFWqv+etk5BDW4g==
|
||||
"@stripe/stripe-js@1.27.0":
|
||||
version "1.27.0"
|
||||
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.27.0.tgz#ab0c82fa89fd40260de4414f69868b769e810550"
|
||||
integrity sha512-SEiybUBu+tlsFKuzdFFydxxjkbrdzHo0tz/naYC5Dt9or/Ux2gcKJBPYQ4RmqQCNHFxgyNj6UYsclywwhe2inQ==
|
||||
|
||||
"@tailwindcss/forms@0.5.0":
|
||||
version "0.5.0"
|
||||
|
@ -923,10 +923,10 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0:
|
|||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
||||
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
||||
|
||||
eslint@8.12.0:
|
||||
version "8.12.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.12.0.tgz#c7a5bd1cfa09079aae64c9076c07eada66a46e8e"
|
||||
integrity sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==
|
||||
eslint@8.13.0:
|
||||
version "8.13.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7"
|
||||
integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==
|
||||
dependencies:
|
||||
"@eslint/eslintrc" "^1.2.1"
|
||||
"@humanwhocodes/config-array" "^0.9.2"
|
||||
|
@ -1644,14 +1644,7 @@ mini-svg-data-uri@^1.2.3:
|
|||
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz#91d2c09f45e056e5e1043340b8b37ba7b50f4fac"
|
||||
integrity sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^3.1.2:
|
||||
minimatch@^3.0.4, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||
|
@ -1965,16 +1958,7 @@ pretty-bytes@6.0.0:
|
|||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.0.0.tgz#928be2ad1f51a2e336add8ba764739f9776a8140"
|
||||
integrity sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==
|
||||
|
||||
prop-types@^15.7.2:
|
||||
version "15.7.2"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
object-assign "^4.1.1"
|
||||
react-is "^16.8.1"
|
||||
|
||||
prop-types@^15.8.1:
|
||||
prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
|
@ -2036,7 +2020,7 @@ react-fast-compare@^2.0.1:
|
|||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||
|
||||
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
|
||||
react-is@^16.13.1, react-is@^16.7.0:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
@ -2264,10 +2248,10 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
|||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
stripe@8.215.0:
|
||||
version "8.215.0"
|
||||
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.215.0.tgz#bb464e256fb83da9ea2f514711fd0f6f7ae7dc9a"
|
||||
integrity sha512-M+7iTZ9bzTkU1Ms+Zsuh0mTQfEzOjMoqyEaVBpuUmdbWTvshavzpAihsOkfabEu+sNY0vdbQxxHZ4kI3W8pKHQ==
|
||||
stripe@8.216.0:
|
||||
version "8.216.0"
|
||||
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.216.0.tgz#23c047498526d13a238c3aca7b4dc8cbbd522e46"
|
||||
integrity sha512-LY8cNGizEnklIa4T82l6mZW0HS4cfzo1hNuhT+ZR9PBkmYcSUbg3ilUBVF0FCd4RP+NA44VEVfoSTTZ1Gg5+rQ==
|
||||
dependencies:
|
||||
"@types/node" ">=8.1.0"
|
||||
qs "^6.10.3"
|
||||
|
@ -2296,10 +2280,10 @@ supports-preserve-symlinks-flag@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
swr@1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.2.tgz#6cae09928d30593a7980d80f85823e57468fac5d"
|
||||
integrity sha512-ky0BskS/V47GpW8d6RU7CPsr6J8cr7mQD6+do5eky3bM0IyJaoi3vO8UhvrzJaObuTlGhPl2szodeB2dUd76Xw==
|
||||
swr@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8"
|
||||
integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==
|
||||
|
||||
tailwindcss@3.0.23:
|
||||
version "3.0.23"
|
||||
|
|
|
@ -8,12 +8,14 @@ const port = Number(process.env.DNSLINK_API_PORT) || 3100;
|
|||
const server = express();
|
||||
|
||||
const dnslinkNamespace = "skynet-ns";
|
||||
const sponsorNamespace = "skynet-sponsor-key";
|
||||
const dnslinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/.+$`);
|
||||
const sponsorRegExp = new RegExp(`^${sponsorNamespace}=[a-zA-Z0-9]+$`);
|
||||
const dnslinkSkylinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/([a-zA-Z0-9_-]{46}|[a-z0-9]{55})`);
|
||||
const hint = `valid example: dnslink=/${dnslinkNamespace}/3ACpC9Umme41zlWUgMQh1fw0sNwgWwyfDDhRQ9Sppz9hjQ`;
|
||||
|
||||
server.get("/dnslink/:name", async (req, res) => {
|
||||
const success = (skylink) => res.send(skylink);
|
||||
const success = (response) => res.json(response);
|
||||
const failure = (message) => res.status(400).send(message);
|
||||
|
||||
if (!isValidDomain(req.params.name)) {
|
||||
|
@ -22,7 +24,7 @@ server.get("/dnslink/:name", async (req, res) => {
|
|||
|
||||
const lookup = `_dnslink.${req.params.name}`;
|
||||
|
||||
dns.resolveTxt(lookup, (error, records) => {
|
||||
dns.resolveTxt(lookup, (error, addresses) => {
|
||||
if (error) {
|
||||
if (error.code === "ENOTFOUND") {
|
||||
return failure(`ENOTFOUND: ${lookup} TXT record doesn't exist`);
|
||||
|
@ -35,11 +37,12 @@ server.get("/dnslink/:name", async (req, res) => {
|
|||
return failure(`Failed to fetch ${lookup} TXT record: ${error.message}`);
|
||||
}
|
||||
|
||||
if (records.length === 0) {
|
||||
if (addresses.length === 0) {
|
||||
return failure(`No TXT record found for ${lookup}`);
|
||||
}
|
||||
|
||||
const dnslinks = records.flat().filter((record) => dnslinkRegExp.test(record));
|
||||
const records = addresses.flat();
|
||||
const dnslinks = records.filter((record) => dnslinkRegExp.test(record));
|
||||
|
||||
if (dnslinks.length === 0) {
|
||||
return failure(`TXT records for ${lookup} found but none of them contained valid skynet dnslink - ${hint}`);
|
||||
|
@ -58,9 +61,25 @@ server.get("/dnslink/:name", async (req, res) => {
|
|||
|
||||
const skylink = matchSkylink[1];
|
||||
|
||||
// check if _dnslink records contain skynet-sponsor-key entries
|
||||
const sponsors = records.filter((record) => sponsorRegExp.test(record));
|
||||
|
||||
if (sponsors.length > 1) {
|
||||
return failure(`Multiple TXT records with valid sponsor key found for ${lookup}, only one allowed`);
|
||||
}
|
||||
|
||||
if (sponsors.length === 1) {
|
||||
// extract just the key part from the record
|
||||
const sponsor = sponsors[0].substring(sponsors[0].indexOf("=") + 1);
|
||||
|
||||
console.log(`${req.params.name} => ${skylink} | sponsor: ${sponsor}`);
|
||||
|
||||
return success({ skylink, sponsor });
|
||||
}
|
||||
|
||||
console.log(`${req.params.name} => ${skylink}`);
|
||||
|
||||
return success(skylink);
|
||||
return success({ skylink });
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
"express": "^4.17.3",
|
||||
"form-data": "^4.0.0",
|
||||
"got": "^11.8.2",
|
||||
"graceful-fs": "^4.2.9",
|
||||
"graceful-fs": "^4.2.10",
|
||||
"hasha": "^5.2.2",
|
||||
"http-status-codes": "^2.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lowdb": "^1.0.0",
|
||||
"skynet-js": "^4.0.19-beta",
|
||||
"write-file-atomic": "^4.0.1",
|
||||
"yargs": "^17.4.0"
|
||||
"yargs": "^17.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^27.5.1",
|
||||
|
|
|
@ -1516,10 +1516,10 @@ got@^11.8.2:
|
|||
p-cancelable "^2.0.0"
|
||||
responselike "^2.0.0"
|
||||
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.9:
|
||||
version "4.2.9"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
|
||||
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.10, graceful-fs@^4.2.9:
|
||||
version "4.2.10"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
has-flag@^3.0.0:
|
||||
version "3.0.0"
|
||||
|
@ -3361,10 +3361,10 @@ yargs@^16.2.0:
|
|||
y18n "^5.0.5"
|
||||
yargs-parser "^20.2.2"
|
||||
|
||||
yargs@^17.4.0:
|
||||
version "17.4.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.0.tgz#9fc9efc96bd3aa2c1240446af28499f0e7593d00"
|
||||
integrity sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==
|
||||
yargs@^17.4.1:
|
||||
version "17.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284"
|
||||
integrity sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==
|
||||
dependencies:
|
||||
cliui "^7.0.2"
|
||||
escalade "^3.1.1"
|
||||
|
|
|
@ -12,16 +12,16 @@
|
|||
"classnames": "2.3.1",
|
||||
"copy-text-to-clipboard": "3.0.1",
|
||||
"crypto-browserify": "3.12.0",
|
||||
"framer-motion": "6.2.8",
|
||||
"gatsby": "4.11.1",
|
||||
"framer-motion": "6.2.10",
|
||||
"gatsby": "4.12.1",
|
||||
"gatsby-background-image": "1.6.0",
|
||||
"gatsby-plugin-image": "2.11.1",
|
||||
"gatsby-plugin-manifest": "4.11.1",
|
||||
"gatsby-plugin-postcss": "5.10.0",
|
||||
"gatsby-plugin-postcss": "5.12.1",
|
||||
"gatsby-plugin-react-helmet": "5.10.0",
|
||||
"gatsby-plugin-robots-txt": "1.7.0",
|
||||
"gatsby-plugin-sharp": "4.10.2",
|
||||
"gatsby-plugin-sitemap": "5.10.2",
|
||||
"gatsby-plugin-robots-txt": "1.7.1",
|
||||
"gatsby-plugin-sharp": "4.12.1",
|
||||
"gatsby-plugin-sitemap": "5.11.1",
|
||||
"gatsby-plugin-svgr": "3.0.0-beta.0",
|
||||
"gatsby-source-filesystem": "4.10.1",
|
||||
"gatsby-transformer-sharp": "4.10.0",
|
||||
|
@ -37,7 +37,7 @@
|
|||
"prop-types": "15.8.1",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2",
|
||||
"react-dropzone": "12.0.4",
|
||||
"react-dropzone": "12.0.5",
|
||||
"react-helmet": "6.1.0",
|
||||
"react-use": "17.3.2",
|
||||
"skynet-js": "4.0.26-beta",
|
||||
|
@ -49,8 +49,8 @@
|
|||
"autoprefixer": "10.4.4",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "9.5.2",
|
||||
"prettier": "2.6.1",
|
||||
"tailwindcss": "3.0.23"
|
||||
"prettier": "2.6.2",
|
||||
"tailwindcss": "3.0.24"
|
||||
},
|
||||
"keywords": [
|
||||
"gatsby"
|
||||
|
|
|
@ -2594,10 +2594,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
|
||||
integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==
|
||||
|
||||
"@types/sharp@^0.29.5":
|
||||
version "0.29.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.29.5.tgz#9c7032d30d138ad16dde6326beaff2af757b91b3"
|
||||
integrity sha512-3TC+S3H5RwnJmLYMHrcdfNjz/CaApKmujjY9b6PU/pE6n0qfooi99YqXGWoW8frU9EWYj/XTI35Pzxa+ThAZ5Q==
|
||||
"@types/sharp@^0.30.0":
|
||||
version "0.30.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.30.2.tgz#df5ff34140b3bad165482e6f3d26b08e42a0503a"
|
||||
integrity sha512-uLCBwjDg/BTcQit0dpNGvkIjvH3wsb8zpaJePCjvONBBSfaKHoxXBIuq1MT8DMQEfk2fKYnpC9QExCgFhkGkMQ==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
|
@ -3339,23 +3339,23 @@ babel-plugin-polyfill-regenerator@^0.3.0:
|
|||
dependencies:
|
||||
"@babel/helper-define-polyfill-provider" "^0.3.1"
|
||||
|
||||
babel-plugin-remove-graphql-queries@^4.11.1:
|
||||
version "4.11.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-4.11.1.tgz#6f107865e8c1a83807c4b48b2262f5e0e0ba537e"
|
||||
integrity sha512-Bqbeow4Xf+Vm4YhAucRGJjf9pNAXakSndYiLKfvef/W6mdtBh00SM8FMaX0U3rtR7ZUXV63RmIyOybVQ6SWCyg==
|
||||
babel-plugin-remove-graphql-queries@^4.11.1, babel-plugin-remove-graphql-queries@^4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-4.12.1.tgz#08e7531ed3c61aaa3c2f083ddce8040844e611d4"
|
||||
integrity sha512-z4Z0VkDpmoIW3cihPYEb+HJMgwa+RF77LnpgAC6y6ozS76ci3ENqfIry/vvdD6auys5TG3xYZ0eHpdPobXzhfA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
|
||||
babel-plugin-transform-react-remove-prop-types@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a"
|
||||
integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==
|
||||
|
||||
babel-preset-gatsby@^2.11.1:
|
||||
version "2.11.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-gatsby/-/babel-preset-gatsby-2.11.1.tgz#860d8d9903df38c314fa6f0cfdb197d02555c4e4"
|
||||
integrity sha512-NGUNAIb3hzD1Mt97q5T3gSSuVuaqnYFSm7AvgByDa3Mk2ohF5Ni86sCLVPRIntIzJvgU5OWY4Qz+6rrI1SwprQ==
|
||||
babel-preset-gatsby@^2.12.1:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-gatsby/-/babel-preset-gatsby-2.12.1.tgz#33d904dc54d5395e049fb346015eba1dbd62bfbf"
|
||||
integrity sha512-ozpDqxxQa32gZVeXO07S0jLJvfewzMLAytP6QHJvVlHEcDnfo7sTo/r3ZNm+2SzeHP51eTDuTFo46WWQnY5kMw==
|
||||
dependencies:
|
||||
"@babel/plugin-proposal-class-properties" "^7.14.0"
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.5"
|
||||
|
@ -3370,8 +3370,8 @@ babel-preset-gatsby@^2.11.1:
|
|||
babel-plugin-dynamic-import-node "^2.3.3"
|
||||
babel-plugin-macros "^2.8.0"
|
||||
babel-plugin-transform-react-remove-prop-types "^0.4.24"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-legacy-polyfills "^2.11.0"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
gatsby-legacy-polyfills "^2.12.1"
|
||||
|
||||
backo2@^1.0.2, backo2@~1.0.2:
|
||||
version "1.0.2"
|
||||
|
@ -3566,7 +3566,7 @@ braces@^2.3.1:
|
|||
split-string "^3.0.2"
|
||||
to-regex "^3.0.1"
|
||||
|
||||
braces@^3.0.1, braces@~3.0.2:
|
||||
braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
|
||||
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
|
||||
|
@ -4340,10 +4340,10 @@ create-ecdh@^4.0.0:
|
|||
bn.js "^4.1.0"
|
||||
elliptic "^6.5.3"
|
||||
|
||||
create-gatsby@^2.11.1:
|
||||
version "2.11.1"
|
||||
resolved "https://registry.yarnpkg.com/create-gatsby/-/create-gatsby-2.11.1.tgz#8d73cce07ff0006386795ca1b74a0bdbb023500b"
|
||||
integrity sha512-ltSLSsbQRoCXxKzgkxp5PBv60O1BL0IdeKKbgmwEcYxiDVw4pXPcFmIqMmvHfk9fqzbCyPzehIQHdlEpJGDYwQ==
|
||||
create-gatsby@^2.12.1:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/create-gatsby/-/create-gatsby-2.12.1.tgz#da5ab2b6dde54d62ec853b699c5aceafb0f166a2"
|
||||
integrity sha512-dOsEy9feLJVFVzFFnA6xJL9OhfYcKewaGMqI9uUaUdifIehBjb5jdeWi+cNy49j2FQLMm38jfZ2SNSQjEK2yOw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
|
||||
|
@ -4710,10 +4710,10 @@ debug@^3.0.0, debug@^3.1.0, debug@^3.2.6, debug@^3.2.7:
|
|||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@~4.3.1:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||
debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1:
|
||||
version "4.3.4"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
|
||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||
dependencies:
|
||||
ms "2.1.2"
|
||||
|
||||
|
@ -5384,15 +5384,15 @@ eslint-plugin-jsx-a11y@^6.5.1:
|
|||
language-tags "^1.0.5"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
eslint-plugin-react-hooks@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172"
|
||||
integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==
|
||||
eslint-plugin-react-hooks@^4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.4.0.tgz#71c39e528764c848d8253e1aa2c7024ed505f6c4"
|
||||
integrity sha512-U3RVIfdzJaeKDQKEJbz5p3NW8/L80PCATJAfuojwbaEL+gBjfGdhUcGde+WGUW46Q5sr/NgxevsIiDtNXrvZaQ==
|
||||
|
||||
eslint-plugin-react@^7.29.2:
|
||||
version "7.29.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.2.tgz#2d4da69d30d0a736efd30890dc6826f3e91f3f7c"
|
||||
integrity sha512-ypEBTKOy5liFQXZWMchJ3LN0JX1uPI6n7MN7OPHKacqXAxq5gYC30TdO7wqGYQyxD1OrzpobdHC3hDmlRWDg9w==
|
||||
eslint-plugin-react@^7.29.4:
|
||||
version "7.29.4"
|
||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.4.tgz#4717de5227f55f3801a5fd51a16a4fa22b5914d2"
|
||||
integrity sha512-CVCXajliVh509PcZYRFyu/BoUEz452+jtQJq2b3Bae4v3xBUWPLCmtmBM+ZinG4MzwmxJgJ2M5rMqhqLVn7MtQ==
|
||||
dependencies:
|
||||
array-includes "^3.1.4"
|
||||
array.prototype.flatmap "^1.2.5"
|
||||
|
@ -6068,10 +6068,10 @@ fragment-cache@^0.2.1:
|
|||
dependencies:
|
||||
map-cache "^0.2.2"
|
||||
|
||||
framer-motion@6.2.8:
|
||||
version "6.2.8"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.2.8.tgz#02abb529191af7e2df444185fe27e932215b715d"
|
||||
integrity sha512-4PtBWFJ6NqR350zYVt9AsFDtISTqsdqna79FvSYPfYDXuuqFmiKtZdkTnYPslnsOMedTW0pEvaQ7eqjD+sA+HA==
|
||||
framer-motion@6.2.10:
|
||||
version "6.2.10"
|
||||
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.2.10.tgz#7fb300d4c559f0991be5499f99d055a04851fcb3"
|
||||
integrity sha512-nfkpA5r3leVOYJH0YXV1cMOLNJuAoznR3Cswet5wCIDi7AZwS62N+u0EmGSNG1JHtglDo5erqyamc55M2XICvA==
|
||||
dependencies:
|
||||
framesync "6.0.1"
|
||||
hey-listen "^1.0.8"
|
||||
|
@ -6158,10 +6158,10 @@ gatsby-background-image@1.6.0:
|
|||
short-uuid "^4.2.0"
|
||||
sort-media-queries "^0.2.2"
|
||||
|
||||
gatsby-cli@^4.11.1:
|
||||
version "4.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.11.1.tgz#a549a91cbd7e7bb9a98413cf604af09d10ef75c2"
|
||||
integrity sha512-RDOFIzKAyysa51x0mMoMtdVhyOX2UkBuEyelGqpuchl8b/ddka/cjEYHk3QRSq55+cBN0/1cTHt/A139ooAKUg==
|
||||
gatsby-cli@^4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.12.1.tgz#157d6dbe783102248af2c938816a58401afeb64b"
|
||||
integrity sha512-vlSqri0p9HpLfACFtUCJhxQArzxSvdcUkrN4Jlw8RgeJYxcJyb8VPPDJHJT3rMGRKZFeBaAeqMbqx/eK4K5F1w==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.14.0"
|
||||
"@babel/core" "^7.15.5"
|
||||
|
@ -6179,13 +6179,13 @@ gatsby-cli@^4.11.1:
|
|||
common-tags "^1.8.2"
|
||||
configstore "^5.0.1"
|
||||
convert-hrtime "^3.0.0"
|
||||
create-gatsby "^2.11.1"
|
||||
create-gatsby "^2.12.1"
|
||||
envinfo "^7.8.1"
|
||||
execa "^5.1.1"
|
||||
fs-exists-cached "^1.0.0"
|
||||
fs-extra "^10.0.0"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-telemetry "^3.11.1"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
gatsby-telemetry "^3.12.1"
|
||||
hosted-git-info "^3.0.8"
|
||||
is-valid-path "^0.1.1"
|
||||
joi "^17.4.2"
|
||||
|
@ -6209,10 +6209,10 @@ gatsby-cli@^4.11.1:
|
|||
yoga-layout-prebuilt "^1.10.0"
|
||||
yurnalist "^2.1.0"
|
||||
|
||||
gatsby-core-utils@^3.10.1, gatsby-core-utils@^3.11.1, gatsby-core-utils@^3.8.2:
|
||||
version "3.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-core-utils/-/gatsby-core-utils-3.11.1.tgz#ea87c1d3aa45c26c9ea32b8e8b029afe6a56f8c7"
|
||||
integrity sha512-Op9/uihtcsDLlZDfRsGJ1ya2mFx2YH9Zmx93bawElZ0YpIzKjCkNTp+I5i5UANxvs5I+Fljl0WHQRudMWg+fWA==
|
||||
gatsby-core-utils@^3.10.1, gatsby-core-utils@^3.11.1, gatsby-core-utils@^3.12.1, gatsby-core-utils@^3.8.2:
|
||||
version "3.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-core-utils/-/gatsby-core-utils-3.12.1.tgz#590ec08de7168b086b7d49128732b9ada95b985f"
|
||||
integrity sha512-jBG1MfR6t2MZNIl8LQ3Cwc92F6uFNcEC091IK+qKVy9FNT0+WzcKQ6Olip6u1NSvCatfrg1FqrH0K78a6lmnLQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
ci-info "2.0.0"
|
||||
|
@ -6222,7 +6222,7 @@ gatsby-core-utils@^3.10.1, gatsby-core-utils@^3.11.1, gatsby-core-utils@^3.8.2:
|
|||
fs-extra "^10.0.0"
|
||||
got "^11.8.3"
|
||||
import-from "^4.0.0"
|
||||
lmdb "^2.2.4"
|
||||
lmdb "^2.2.6"
|
||||
lock "^1.1.0"
|
||||
node-object-hash "^2.3.10"
|
||||
proper-lockfile "^4.1.2"
|
||||
|
@ -6230,49 +6230,49 @@ gatsby-core-utils@^3.10.1, gatsby-core-utils@^3.11.1, gatsby-core-utils@^3.8.2:
|
|||
tmp "^0.2.1"
|
||||
xdg-basedir "^4.0.0"
|
||||
|
||||
gatsby-graphiql-explorer@^2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-graphiql-explorer/-/gatsby-graphiql-explorer-2.11.0.tgz#7e886846482ad72bd49f515e7faa658a94342803"
|
||||
integrity sha512-nMNXlF/pleO/rH66t00SdXdKq3vV0/Su5EEQY7xg3yRc38ueC2UkZq10nrJiVoc05RO8Txo5o2gpoC2DP07lFg==
|
||||
gatsby-graphiql-explorer@^2.12.1:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-graphiql-explorer/-/gatsby-graphiql-explorer-2.12.1.tgz#4fad5b9a3ccbcc4871f70222e8ac1ca880ff844d"
|
||||
integrity sha512-H5phTjIGUiUZxN3C0hogH66lB+qC9HO9O4m4RpHZ3JyxVIvPemGSNmgovhL7+LydS34UY5rbT0UBFwaxrHMZpQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
|
||||
gatsby-legacy-polyfills@^2.11.0:
|
||||
version "2.11.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-legacy-polyfills/-/gatsby-legacy-polyfills-2.11.0.tgz#8b2afc4d97f44eb5767fe9b49f55ff675055ffd2"
|
||||
integrity sha512-ulkRNCitwFjwUM4f2ufljH0WjELm6QEIOGRryNRt9LKJEB9QGmdm+KUAWIv7xrFUqKq1Pn6is64wcfXDw21zSA==
|
||||
gatsby-legacy-polyfills@^2.12.1:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-legacy-polyfills/-/gatsby-legacy-polyfills-2.12.1.tgz#62ad6432b3c17f7f5640786ed00dba88da60ab22"
|
||||
integrity sha512-x2Njk0GsBKsiVBDZHI7nVWDNBPQeonQsElzFEDoSJpW47j9H8PPJDeOUZ+u5q76rtxuQQo/VXl/eD817qRBxAA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
core-js-compat "3.9.0"
|
||||
|
||||
gatsby-link@^4.11.1:
|
||||
version "4.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-link/-/gatsby-link-4.11.1.tgz#f8bfee4c7f3bf0ede255bddf87d0f13c64ed39f2"
|
||||
integrity sha512-wOhdgsnzHr4iYWo3iKadw8jj5PmIu1wbi6LUftwQzFOFvkBaJvC/br1ju8W0nbwSjWG474hTZRon43xDQX9bIw==
|
||||
gatsby-link@^4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-link/-/gatsby-link-4.12.1.tgz#131448e9c51e0c9a65e2a342603d496e85cea2da"
|
||||
integrity sha512-ILWYNqyTlEt2bOVWgzwmbijwC+Ow4CZVbnWOyaQ/jvu5z3ZGL0z5tGGD+sjZAHc8anOMWn/JWhL0BKGVaxjMGQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@types/reach__router" "^1.3.10"
|
||||
gatsby-page-utils "^2.11.1"
|
||||
gatsby-page-utils "^2.12.1"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
gatsby-page-utils@^2.11.1:
|
||||
version "2.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-page-utils/-/gatsby-page-utils-2.11.1.tgz#dd10f99184b64528ae76f2b654b8ed1b23cb9c39"
|
||||
integrity sha512-K1Mbk4CKYZwpJcE4zk4JAff7ZBNFXI0fC8lZwLbDAzVcqYUaouqqqnoU7WeB8HHUqDQi05CXItx1bbZFDGIymw==
|
||||
gatsby-page-utils@^2.12.1:
|
||||
version "2.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-page-utils/-/gatsby-page-utils-2.12.1.tgz#ee5dbd21b8e22003e5b0c7e4d86ed3b89773c0ac"
|
||||
integrity sha512-2NPfVHRoHYcqUZrGVAvHN28uqI/PTGE/DrpE79YR/blbnloEzwzpAGNbBjWitgcR0st5q5NrATJQ/Imu3M7ApA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
bluebird "^3.7.2"
|
||||
chokidar "^3.5.2"
|
||||
fs-exists-cached "^1.0.0"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
glob "^7.2.0"
|
||||
lodash "^4.17.21"
|
||||
micromatch "^4.0.4"
|
||||
micromatch "^4.0.5"
|
||||
|
||||
gatsby-parcel-config@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-parcel-config/-/gatsby-parcel-config-0.2.0.tgz#0b1795d17c825bd293c372fa0acfa987aa91111e"
|
||||
integrity sha512-BbsSm5O0R7IvCRLNSk3lBpkU8RtSOn8s7Ifa7bHF63PzTG1SUpBjwMF6301tCbvdSXWrP7n9dsfaXS6ex/TElQ==
|
||||
gatsby-parcel-config@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-parcel-config/-/gatsby-parcel-config-0.3.1.tgz#424c7b3d65b60e2e454cd9d61d4c5de752aad576"
|
||||
integrity sha512-Wpz6DSKiWeqVyZUNmO7EHy0h9ISG+HfUD8v2g0kN4ZcZjJtSiWvGym1+6Swgjo9bQvy59qa7bO4hKGA9gHvMVg==
|
||||
dependencies:
|
||||
"@gatsbyjs/parcel-namer-relative-to-cwd" "0.0.2"
|
||||
"@parcel/bundler-default" "^2.3.2"
|
||||
|
@ -6322,27 +6322,27 @@ gatsby-plugin-manifest@4.11.1:
|
|||
semver "^7.3.5"
|
||||
sharp "^0.30.1"
|
||||
|
||||
gatsby-plugin-page-creator@^4.11.1:
|
||||
version "4.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-page-creator/-/gatsby-plugin-page-creator-4.11.1.tgz#750f4b773684777cec6caa9266787427ed2630a6"
|
||||
integrity sha512-6XET4qYqu2yVwUU6sO44wSR62zQZdq7BoMvN9OhKpUDBZYLfve9CwufkhZZnQvq+axNZZMUmKa/RqbBXiE6/yA==
|
||||
gatsby-plugin-page-creator@^4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-page-creator/-/gatsby-plugin-page-creator-4.12.1.tgz#36460f5308df8b04bb2b0649150cb87e3eb15c8f"
|
||||
integrity sha512-McVYpXWgneo1+3+8KGrGATgNwAYtZXbFKL8Q18lwH+bt5f2NbYP23g+xGitxT62zvhhzs0AjuEJa7BoTEmFTMQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
"@babel/traverse" "^7.15.4"
|
||||
"@sindresorhus/slugify" "^1.1.2"
|
||||
chokidar "^3.5.2"
|
||||
fs-exists-cached "^1.0.0"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-page-utils "^2.11.1"
|
||||
gatsby-plugin-utils "^3.5.1"
|
||||
gatsby-telemetry "^3.11.1"
|
||||
globby "^11.0.4"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
gatsby-page-utils "^2.12.1"
|
||||
gatsby-plugin-utils "^3.6.1"
|
||||
gatsby-telemetry "^3.12.1"
|
||||
globby "^11.1.0"
|
||||
lodash "^4.17.21"
|
||||
|
||||
gatsby-plugin-postcss@5.10.0:
|
||||
version "5.10.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-postcss/-/gatsby-plugin-postcss-5.10.0.tgz#e241f1671e66f7b660826f39fd26591aae652716"
|
||||
integrity sha512-s1zzysu1kKIqR+CfQeQsG0CCdj2S7tjc4BhCY2a3V4cl7ORJtMx1HGKDUzE9gV/EXRTmr9lhE9Gl+2v8fRouvA==
|
||||
gatsby-plugin-postcss@5.12.1:
|
||||
version "5.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-postcss/-/gatsby-plugin-postcss-5.12.1.tgz#8be5cf7bbfbb27967cad2c5da8dbe1b8c7e576b8"
|
||||
integrity sha512-gNfMl/ZWCZpnCsy+IPzRPzqaPPStbUr/LG8eikGbyoZj1gSjom/a7Hi3z29sWx/vEd4dAHGRPS+n7bjf3iOnNw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
postcss-loader "^4.3.0"
|
||||
|
@ -6354,43 +6354,43 @@ gatsby-plugin-react-helmet@5.10.0:
|
|||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
|
||||
gatsby-plugin-robots-txt@1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-robots-txt/-/gatsby-plugin-robots-txt-1.7.0.tgz#58ac310c9fb7e58162d6e21802884b342837b451"
|
||||
integrity sha512-Y1D8FBeXNtECoCd0g0jIkhKpSvzFzeh2xpt1xTvGluRP6xmqJq7iB3DPEv7xqGlZAcfzaSxw/j5++Y+3WLva8A==
|
||||
gatsby-plugin-robots-txt@1.7.1:
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-robots-txt/-/gatsby-plugin-robots-txt-1.7.1.tgz#f956729e34f6269cc314352e9ef1cf7b4c515a68"
|
||||
integrity sha512-ZdZm8/4b7Whf+W5kf+DqjZwz/+DY+IB7xp227+m2f2rgGUsz8yVCz4RitiN5+EInGFZFry0v+IbrUKCXTpIZYg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.16.7"
|
||||
generate-robotstxt "^8.0.3"
|
||||
|
||||
gatsby-plugin-sharp@4.10.2:
|
||||
version "4.10.2"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-4.10.2.tgz#253a49c452a7409ceece4e541e4770e61a306bcc"
|
||||
integrity sha512-MWzPTYnu7HZ0kctHtkLbZOe6ZGUqSsNATO3lWlSBIFpeimxaPF5iHBiu1CX/ofz4pwt7VamtIzAV28VB6sjONw==
|
||||
gatsby-plugin-sharp@4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-4.12.1.tgz#f97cb29074f4a07b469b9ddb1058a3574928d4eb"
|
||||
integrity sha512-P6noUl5LyASwYtCRSo1rjchk/ytfJvSFTLwzgXr1TiQHgZh06SUIqR8v3UqT90EDERNd1GeEBsQjRfWkrV2nbg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
async "^3.2.3"
|
||||
bluebird "^3.7.2"
|
||||
debug "^4.3.3"
|
||||
debug "^4.3.4"
|
||||
filenamify "^4.3.0"
|
||||
fs-extra "^10.0.0"
|
||||
gatsby-core-utils "^3.10.1"
|
||||
gatsby-plugin-utils "^3.4.2"
|
||||
gatsby-telemetry "^3.10.1"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
gatsby-plugin-utils "^3.6.1"
|
||||
gatsby-telemetry "^3.12.1"
|
||||
got "^11.8.3"
|
||||
lodash "^4.17.21"
|
||||
mini-svg-data-uri "^1.4.3"
|
||||
mini-svg-data-uri "^1.4.4"
|
||||
potrace "^2.1.8"
|
||||
probe-image-size "^7.0.0"
|
||||
probe-image-size "^7.2.3"
|
||||
progress "^2.0.3"
|
||||
semver "^7.3.5"
|
||||
sharp "^0.30.1"
|
||||
sharp "^0.30.3"
|
||||
svgo "1.3.2"
|
||||
uuid "3.4.0"
|
||||
|
||||
gatsby-plugin-sitemap@5.10.2:
|
||||
version "5.10.2"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-sitemap/-/gatsby-plugin-sitemap-5.10.2.tgz#208149b900b166c42aa88a5f5436f5c6bf6561e9"
|
||||
integrity sha512-X6pVbytl/QfdfGrnXAEKPf5vc38WIbclmHYIfbgjXUYA9yckTxnfuYZqkS2YwCmbcUTHG1ugcmXMeBGVo77IBQ==
|
||||
gatsby-plugin-sitemap@5.11.1:
|
||||
version "5.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-sitemap/-/gatsby-plugin-sitemap-5.11.1.tgz#863397fe9dd5aab89bda8db09ef9b877c960150e"
|
||||
integrity sha512-tt92KLUDS+eCrqSA5oYieDGjXLyUDXfYKEwLhYKXk7KlMMjporFJWVrc4Ba8WD04bUWVnzc2rqr19/zQI0ZIpQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
common-tags "^1.8.2"
|
||||
|
@ -6402,10 +6402,10 @@ gatsby-plugin-svgr@3.0.0-beta.0:
|
|||
resolved "https://registry.yarnpkg.com/gatsby-plugin-svgr/-/gatsby-plugin-svgr-3.0.0-beta.0.tgz#7e5315f51dae2663a447899322ea1487cef93dd6"
|
||||
integrity sha512-oALTh6VwO6l3khgC/vGr706aqt38EkXwdr6iXVei/auOKGxpCLEuDCQVal1a4SpYXdjHjRsEyab6bxaHL2lzsA==
|
||||
|
||||
gatsby-plugin-typescript@^4.11.1:
|
||||
version "4.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-typescript/-/gatsby-plugin-typescript-4.11.1.tgz#dcd96ded685f8c4a73ae5524faab342f9c9e3c1d"
|
||||
integrity sha512-6ef2wRhPqcLPyekEAU3xcoqI59r+mDnCzn/O+8hRgwJyx/2dwvF8brusetXoqdTk4Vyhk44p8dog8+gCGATckw==
|
||||
gatsby-plugin-typescript@^4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-typescript/-/gatsby-plugin-typescript-4.12.1.tgz#8fe51b57f1fca5027f5dd558b73364a4c34a0a38"
|
||||
integrity sha512-7ZzGTL+hNGGmiIk4j4QSZYyYsy4i9EW/zgK/IJwmpSBNzoagI/Pz64ntNWpxZstfgzkuIYZfvuvj3Ao9mKF5aw==
|
||||
dependencies:
|
||||
"@babel/core" "^7.15.5"
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.5"
|
||||
|
@ -6413,37 +6413,37 @@ gatsby-plugin-typescript@^4.11.1:
|
|||
"@babel/plugin-proposal-optional-chaining" "^7.14.5"
|
||||
"@babel/preset-typescript" "^7.15.0"
|
||||
"@babel/runtime" "^7.15.4"
|
||||
babel-plugin-remove-graphql-queries "^4.11.1"
|
||||
babel-plugin-remove-graphql-queries "^4.12.1"
|
||||
|
||||
gatsby-plugin-utils@^3.4.2, gatsby-plugin-utils@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-utils/-/gatsby-plugin-utils-3.5.1.tgz#9aed9deec0f4ee82bfc7390f735b9455ca4f8494"
|
||||
integrity sha512-RZXUvwQjTnkukMfAGr+DCz/qZj7g6REljTmQS43MaovWO4Yf4YGvs+1Leays7J0XmqN2I3SIZGBgt4tgKCsNVQ==
|
||||
gatsby-plugin-utils@^3.5.1, gatsby-plugin-utils@^3.6.1:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-utils/-/gatsby-plugin-utils-3.6.1.tgz#31d742e1aded08439ad42959880821e1fc9740cd"
|
||||
integrity sha512-Ebk98v4mxaDWjGFl6VBeNv1zjeJ7UCQ29UTabzY2BpztvUCBHfLVQdMmuaAgzPRn+A3SFVOGpcl++CF0IEl+7A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
fs-extra "^10.0.0"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-sharp "^0.5.0"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
gatsby-sharp "^0.6.1"
|
||||
graphql-compose "^9.0.7"
|
||||
import-from "^4.0.0"
|
||||
joi "^17.4.2"
|
||||
mime "^3.0.0"
|
||||
|
||||
gatsby-react-router-scroll@^5.11.0:
|
||||
version "5.11.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-react-router-scroll/-/gatsby-react-router-scroll-5.11.0.tgz#866b89366146d8df3852ed699d12be1e9fce4acc"
|
||||
integrity sha512-g/lyG0X73cpI9DdYvCv5rZiV8LqHjn6q1l8Vfm/jBS7wtv8XxNR4BxUqkbMeHRcvZcX5bXku6FFSFUAOd9c3QQ==
|
||||
gatsby-react-router-scroll@^5.12.1:
|
||||
version "5.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-react-router-scroll/-/gatsby-react-router-scroll-5.12.1.tgz#10fdf43c1179ae53e7726c6c8d894139e7862e8f"
|
||||
integrity sha512-zZCTiicALh6eSsQAgIhSCmQm6Dl6fY6eaKmOXGMMbVtUKmiGxikh2MFN6S5J5JU9MV/piSheVqYkouyTDGXbuw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.15.4"
|
||||
prop-types "^15.7.2"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
gatsby-sharp@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-sharp/-/gatsby-sharp-0.5.0.tgz#879d3c462eefa917cb3a50c6ec891951d9740f56"
|
||||
integrity sha512-9wZS0ADZsKTCsU66sxIP/tCHgFaREyoYm53tepgtp/YSVWNrurx9/0kGf8XsFFY9OecrqIRNuk1cWe7XKCpbQA==
|
||||
gatsby-sharp@^0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-sharp/-/gatsby-sharp-0.6.1.tgz#48805690cb020111722cc25445b4b662446da6d0"
|
||||
integrity sha512-KhBFE72QLlrAgeMWNoBV2LDp0nZ9ZOw1pY5wIohb/ktDFRUi9K5nwVCJvDJonfPn100mxtDqnZVckXirtcHVzQ==
|
||||
dependencies:
|
||||
"@types/sharp" "^0.29.5"
|
||||
sharp "^0.30.1"
|
||||
"@types/sharp" "^0.30.0"
|
||||
sharp "^0.30.3"
|
||||
|
||||
gatsby-source-filesystem@4.10.1:
|
||||
version "4.10.1"
|
||||
|
@ -6463,10 +6463,10 @@ gatsby-source-filesystem@4.10.1:
|
|||
valid-url "^1.0.9"
|
||||
xstate "^4.26.1"
|
||||
|
||||
gatsby-telemetry@^3.10.1, gatsby-telemetry@^3.11.1:
|
||||
version "3.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-3.11.1.tgz#87caed899143276056e9af20ab38c15ad9dcdf52"
|
||||
integrity sha512-TPNKTpuYFyULOuRvhpXUtj8h2E7bvrTYsRC/aKeHoWqEchwwbzPwBSJd+3ZFjsxLHIXAa5sTAlR2wd9SYBgOlA==
|
||||
gatsby-telemetry@^3.12.1:
|
||||
version "3.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-3.12.1.tgz#a3508a45d95f2c3457db7dbe2628560d00c43beb"
|
||||
integrity sha512-sAL2T9GdYpceGlFP6CymVDoy0UEhRvrJApv/mu7sU6F0gu8g8rOLvRxVYE3Y2D9RdfCzkuLIonzmscmVIduyOg==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.14.0"
|
||||
"@babel/runtime" "^7.15.4"
|
||||
|
@ -6476,7 +6476,7 @@ gatsby-telemetry@^3.10.1, gatsby-telemetry@^3.11.1:
|
|||
boxen "^4.2.0"
|
||||
configstore "^5.0.1"
|
||||
fs-extra "^10.0.0"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
git-up "^4.0.5"
|
||||
is-docker "^2.2.1"
|
||||
lodash "^4.17.21"
|
||||
|
@ -6506,18 +6506,18 @@ gatsby-transformer-yaml@4.11.0:
|
|||
lodash "^4.17.21"
|
||||
unist-util-select "^1.5.0"
|
||||
|
||||
gatsby-worker@^1.11.0:
|
||||
version "1.11.0"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-worker/-/gatsby-worker-1.11.0.tgz#bf8c3b9374390260b8335d7cfccbc332d0716727"
|
||||
integrity sha512-uJ5bNrifIrS20o0SYkmb379logfRKO35cqYxd2R0uNf9kWGaQOda0SZfm7Uw+Vdx7cO9Ra8p1ArijbHm7ZArCA==
|
||||
gatsby-worker@^1.12.1:
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby-worker/-/gatsby-worker-1.12.1.tgz#553a0fd5ad796567fc3ab965490270ff7b0ae680"
|
||||
integrity sha512-9slhXsK1/N4nJK+Yia84PL/zvNqV/bqD820W4R2f5jh5gEnVYrY2TcnG6A+UDbY7orhS0CLf1mMW9WKd6u6CUA==
|
||||
dependencies:
|
||||
"@babel/core" "^7.15.5"
|
||||
"@babel/runtime" "^7.15.4"
|
||||
|
||||
gatsby@4.11.1:
|
||||
version "4.11.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.11.1.tgz#ffe7754c9c368fd746bdeca808572641a378addb"
|
||||
integrity sha512-ffEXb/mvZtB0cQ8javEkhruubxjTbZSsN81IYGGY/ym4YB+Zm1a8K0NV7DsRGsPO9nx7Z/D/OBVxVmse1Nnxzw==
|
||||
gatsby@4.12.1:
|
||||
version "4.12.1"
|
||||
resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.12.1.tgz#b9a72bca167662a05d7154e5e6091f31aa333efd"
|
||||
integrity sha512-/QteQShPAW1dRmG9wrjHmdfQEQxh6WfOi9jnJXAxljAx8UlRt0JFntxMc9gWGUJD6fXYKmf13Jan9izuNDQxNQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.14.0"
|
||||
"@babel/core" "^7.15.5"
|
||||
|
@ -6544,8 +6544,8 @@ gatsby@4.11.1:
|
|||
babel-plugin-add-module-exports "^1.0.4"
|
||||
babel-plugin-dynamic-import-node "^2.3.3"
|
||||
babel-plugin-lodash "^3.3.4"
|
||||
babel-plugin-remove-graphql-queries "^4.11.1"
|
||||
babel-preset-gatsby "^2.11.1"
|
||||
babel-plugin-remove-graphql-queries "^4.12.1"
|
||||
babel-preset-gatsby "^2.12.1"
|
||||
better-opn "^2.1.1"
|
||||
bluebird "^3.7.2"
|
||||
body-parser "^1.19.0"
|
||||
|
@ -6574,8 +6574,8 @@ gatsby@4.11.1:
|
|||
eslint-plugin-graphql "^4.0.0"
|
||||
eslint-plugin-import "^2.25.4"
|
||||
eslint-plugin-jsx-a11y "^6.5.1"
|
||||
eslint-plugin-react "^7.29.2"
|
||||
eslint-plugin-react-hooks "^4.3.0"
|
||||
eslint-plugin-react "^7.29.4"
|
||||
eslint-plugin-react-hooks "^4.4.0"
|
||||
eslint-webpack-plugin "^2.6.0"
|
||||
event-source-polyfill "^1.0.25"
|
||||
execa "^5.1.1"
|
||||
|
@ -6587,19 +6587,19 @@ gatsby@4.11.1:
|
|||
find-cache-dir "^3.3.2"
|
||||
fs-exists-cached "1.0.0"
|
||||
fs-extra "^10.0.0"
|
||||
gatsby-cli "^4.11.1"
|
||||
gatsby-core-utils "^3.11.1"
|
||||
gatsby-graphiql-explorer "^2.11.0"
|
||||
gatsby-legacy-polyfills "^2.11.0"
|
||||
gatsby-link "^4.11.1"
|
||||
gatsby-page-utils "^2.11.1"
|
||||
gatsby-parcel-config "^0.2.0"
|
||||
gatsby-plugin-page-creator "^4.11.1"
|
||||
gatsby-plugin-typescript "^4.11.1"
|
||||
gatsby-plugin-utils "^3.5.1"
|
||||
gatsby-react-router-scroll "^5.11.0"
|
||||
gatsby-telemetry "^3.11.1"
|
||||
gatsby-worker "^1.11.0"
|
||||
gatsby-cli "^4.12.1"
|
||||
gatsby-core-utils "^3.12.1"
|
||||
gatsby-graphiql-explorer "^2.12.1"
|
||||
gatsby-legacy-polyfills "^2.12.1"
|
||||
gatsby-link "^4.12.1"
|
||||
gatsby-page-utils "^2.12.1"
|
||||
gatsby-parcel-config "^0.3.1"
|
||||
gatsby-plugin-page-creator "^4.12.1"
|
||||
gatsby-plugin-typescript "^4.12.1"
|
||||
gatsby-plugin-utils "^3.6.1"
|
||||
gatsby-react-router-scroll "^5.12.1"
|
||||
gatsby-telemetry "^3.12.1"
|
||||
gatsby-worker "^1.12.1"
|
||||
glob "^7.2.0"
|
||||
globby "^11.1.0"
|
||||
got "^11.8.2"
|
||||
|
@ -6614,7 +6614,7 @@ gatsby@4.11.1:
|
|||
joi "^17.4.2"
|
||||
json-loader "^0.5.7"
|
||||
latest-version "5.1.0"
|
||||
lmdb "^2.2.3"
|
||||
lmdb "~2.2.3"
|
||||
lodash "^4.17.21"
|
||||
md5-file "^5.0.0"
|
||||
meant "^1.0.3"
|
||||
|
@ -6672,7 +6672,7 @@ gatsby@4.11.1:
|
|||
xstate "^4.26.0"
|
||||
yaml-loader "^0.6.0"
|
||||
optionalDependencies:
|
||||
gatsby-sharp "^0.5.0"
|
||||
gatsby-sharp "^0.6.1"
|
||||
|
||||
gauge@~2.7.3:
|
||||
version "2.7.4"
|
||||
|
@ -6894,7 +6894,7 @@ globby@11.0.3:
|
|||
merge2 "^1.3.0"
|
||||
slash "^3.0.0"
|
||||
|
||||
globby@^11.0.3, globby@^11.0.4, globby@^11.1.0:
|
||||
globby@^11.0.3, globby@^11.1.0:
|
||||
version "11.1.0"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
|
||||
integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
|
||||
|
@ -8072,10 +8072,10 @@ levn@^0.4.1:
|
|||
prelude-ls "^1.2.1"
|
||||
type-check "~0.4.0"
|
||||
|
||||
lilconfig@^2.0.3, lilconfig@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
|
||||
integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==
|
||||
lilconfig@^2.0.3, lilconfig@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
|
||||
integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
|
||||
|
||||
lines-and-columns@^1.1.6:
|
||||
version "1.2.4"
|
||||
|
@ -8096,6 +8096,36 @@ listr2@^3.8.3:
|
|||
through "^2.3.8"
|
||||
wrap-ansi "^7.0.0"
|
||||
|
||||
lmdb-darwin-arm64@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.3.2.tgz#edcd324e4693abfcd02e7c5ba44a7f0e209f7588"
|
||||
integrity sha512-20lWWUPGKnSZRFY8FBm+vZEFx/5Deh0joz6cqJ8/0SuO/ejqRCppSsNqAxPqW87KUNR5rNfhaA2oRekMeb0cwQ==
|
||||
|
||||
lmdb-darwin-x64@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lmdb-darwin-x64/-/lmdb-darwin-x64-2.3.2.tgz#6561f37d0461c3128b92fb80996c54e6243af939"
|
||||
integrity sha512-BsBnOfgK1B11Dh4RgcgBTmkmsPv3mjBPKsA4W4E+18SW9K2aRi86CAMPXqjfY/OJDUe1pSrpVf1A83b8N/C9rg==
|
||||
|
||||
lmdb-linux-arm64@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lmdb-linux-arm64/-/lmdb-linux-arm64-2.3.2.tgz#feb4a52a0030feb9520543c78919c30250a85107"
|
||||
integrity sha512-DIibLZHpwwlIsP9cBRmw0xqDy6wZH+CDAnOTI+eihQ5PdWjTs+kaQs5O/x8l6/8fwCB0TPYKWTqfdUbvd/F7AA==
|
||||
|
||||
lmdb-linux-arm@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lmdb-linux-arm/-/lmdb-linux-arm-2.3.2.tgz#d8ee33547cc0f671efcf63ca381306df51f972f5"
|
||||
integrity sha512-ofxfxVQqMbaC2Ygjzk8k6xgS5Dg/3cANeLcEx14T35GoU5pQKlLAWjypptyLQEeOboEmEOpZmHMoD7sWu/zakQ==
|
||||
|
||||
lmdb-linux-x64@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lmdb-linux-x64/-/lmdb-linux-x64-2.3.2.tgz#1c66a012199ab5235d3fcec8c3fdad573cf3eff4"
|
||||
integrity sha512-HBUd013RRQ2KpiyBqqqSPSEwPpVUpTJZdTZGDVQFQZuxqyJumt4Wye3uh6ZgEiBtxzSelt4xvAeNjYPH0dcZSQ==
|
||||
|
||||
lmdb-win32-x64@2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/lmdb-win32-x64/-/lmdb-win32-x64-2.3.2.tgz#e65f79bfbb9f09ebfa53d3b03959bc581ebda55a"
|
||||
integrity sha512-/hir5oU+GYm7/B6QirrpyOmIuzCKiIbWoKIJI2ebXeJlrs6Jj7UY9caPBYVkCzd79QzJnB7hIlX/F6Jx6gcUmg==
|
||||
|
||||
lmdb@2.2.4:
|
||||
version "2.2.4"
|
||||
resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.2.4.tgz#6494d5a1d1db152e0be759edcfa06893e4cbdb53"
|
||||
|
@ -8107,7 +8137,26 @@ lmdb@2.2.4:
|
|||
ordered-binary "^1.2.4"
|
||||
weak-lru-cache "^1.2.2"
|
||||
|
||||
lmdb@^2.0.2, lmdb@^2.2.3, lmdb@^2.2.4:
|
||||
lmdb@^2.0.2, lmdb@^2.2.6:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.3.3.tgz#ee0c53a5f5c2509c566b891803b3322e5a59095f"
|
||||
integrity sha512-CrooSvHOzd+jPXCXpiffu2+5m90Fe6L/cw90fg+4sCWNrw3W7/ad20CGuTkMVU7mAuwXEAJAfnUwvHN2pS9Rqg==
|
||||
dependencies:
|
||||
msgpackr "^1.5.4"
|
||||
nan "^2.14.2"
|
||||
node-addon-api "^4.3.0"
|
||||
node-gyp-build-optional-packages "^4.3.2"
|
||||
ordered-binary "^1.2.4"
|
||||
weak-lru-cache "^1.2.2"
|
||||
optionalDependencies:
|
||||
lmdb-darwin-arm64 "2.3.2"
|
||||
lmdb-darwin-x64 "2.3.2"
|
||||
lmdb-linux-arm "2.3.2"
|
||||
lmdb-linux-arm64 "2.3.2"
|
||||
lmdb-linux-x64 "2.3.2"
|
||||
lmdb-win32-x64 "2.3.2"
|
||||
|
||||
lmdb@~2.2.3:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.2.6.tgz#a52ef533812b8abcbe0033fc9d74d215e7dfc0a0"
|
||||
integrity sha512-UmQV0oZZcV3EN6rjcAjIiuWcc3MYZGWQ0GUYz46Ron5fuTa/dUow7WSQa6leFkvZIKVUdECBWVw96tckfEzUFQ==
|
||||
|
@ -8579,13 +8628,13 @@ micromatch@^3.1.10:
|
|||
snapdragon "^0.8.1"
|
||||
to-regex "^3.0.2"
|
||||
|
||||
micromatch@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
||||
integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==
|
||||
micromatch@^4.0.4, micromatch@^4.0.5:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
|
||||
integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
|
||||
dependencies:
|
||||
braces "^3.0.1"
|
||||
picomatch "^2.2.3"
|
||||
braces "^3.0.2"
|
||||
picomatch "^2.3.1"
|
||||
|
||||
miller-rabin@^4.0.0:
|
||||
version "4.0.1"
|
||||
|
@ -8668,10 +8717,10 @@ mini-css-extract-plugin@1.6.2:
|
|||
schema-utils "^3.0.0"
|
||||
webpack-sources "^1.1.0"
|
||||
|
||||
mini-svg-data-uri@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.3.tgz#43177b2e93766ba338931a3e2a84a3dfd3a222b8"
|
||||
integrity sha512-gSfqpMRC8IxghvMcxzzmMnWpXAChSA+vy4cia33RgerMS8Fex95akUyQZPbxJJmeBGiGmK7n/1OpUX8ksRjIdA==
|
||||
mini-svg-data-uri@^1.4.4:
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939"
|
||||
integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
@ -8737,9 +8786,9 @@ mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.1:
|
|||
minimist "^1.2.5"
|
||||
|
||||
moment@^2.29.1:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
version "2.29.2"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
|
||||
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -8919,6 +8968,11 @@ node-fetch@^2.6.1, node-fetch@^2.6.6, node-fetch@^2.6.7:
|
|||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-gyp-build-optional-packages@^4.3.2:
|
||||
version "4.3.2"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-4.3.2.tgz#82de9bdf9b1ad042457533afb2f67469dc2264bb"
|
||||
integrity sha512-P5Ep3ISdmwcCkZIaBaQamQtWAG0facC89phWZgi5Z3hBU//J6S48OIvyZWSPPf6yQMklLZiqoosWAZUj7N+esA==
|
||||
|
||||
node-gyp-build@^4.2.3, node-gyp-build@^4.3.0:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3"
|
||||
|
@ -9051,10 +9105,10 @@ object-copy@^0.1.0:
|
|||
define-property "^0.2.5"
|
||||
kind-of "^3.0.3"
|
||||
|
||||
object-hash@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
|
||||
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
|
||||
object-hash@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9"
|
||||
integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
|
||||
|
||||
object-inspect@^1.11.0, object-inspect@^1.9.0:
|
||||
version "1.12.0"
|
||||
|
@ -9509,7 +9563,7 @@ picocolors@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
@ -9639,12 +9693,12 @@ postcss-js@^4.0.0:
|
|||
dependencies:
|
||||
camelcase-css "^2.0.1"
|
||||
|
||||
postcss-load-config@^3.1.0:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.3.tgz#21935b2c43b9a86e6581a576ca7ee1bde2bd1d23"
|
||||
integrity sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw==
|
||||
postcss-load-config@^3.1.4:
|
||||
version "3.1.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
|
||||
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
|
||||
dependencies:
|
||||
lilconfig "^2.0.4"
|
||||
lilconfig "^2.0.5"
|
||||
yaml "^1.10.2"
|
||||
|
||||
postcss-loader@^4.3.0:
|
||||
|
@ -9838,10 +9892,10 @@ postcss-reduce-transforms@^5.1.0:
|
|||
dependencies:
|
||||
postcss-value-parser "^4.2.0"
|
||||
|
||||
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
|
||||
version "6.0.9"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f"
|
||||
integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ==
|
||||
postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
|
||||
version "6.0.10"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
|
||||
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
util-deprecate "^1.0.2"
|
||||
|
@ -9866,7 +9920,7 @@ postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
|
|||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@8.4.12, postcss@^8.2.15, postcss@^8.2.9, postcss@^8.3.11, postcss@^8.4.6:
|
||||
postcss@8.4.12, postcss@^8.2.15, postcss@^8.2.9, postcss@^8.3.11, postcss@^8.4.12:
|
||||
version "8.4.12"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
|
||||
integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
|
||||
|
@ -9911,10 +9965,10 @@ prepend-http@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||
|
||||
prettier@2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
|
||||
integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
|
||||
prettier@2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
|
||||
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
|
||||
|
||||
pretty-bytes@^5.4.1, pretty-bytes@^5.6.0:
|
||||
version "5.6.0"
|
||||
|
@ -9929,7 +9983,7 @@ pretty-error@^2.1.2:
|
|||
lodash "^4.17.20"
|
||||
renderkid "^2.0.4"
|
||||
|
||||
probe-image-size@^7.0.0:
|
||||
probe-image-size@^7.0.0, probe-image-size@^7.2.3:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/probe-image-size/-/probe-image-size-7.2.3.tgz#d49c64be540ec8edea538f6f585f65a9b3ab4309"
|
||||
integrity sha512-HubhG4Rb2UH8YtV4ba0Vp5bQ7L78RTONYu/ujmCu5nBI8wGv24s4E9xSKBi0N1MowRpxk76pFCpJtW0KPzOK0w==
|
||||
|
@ -10209,10 +10263,10 @@ react-dom@17.0.2:
|
|||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-dropzone@12.0.4:
|
||||
version "12.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-12.0.4.tgz#b88eeaa2c7118f7fd042404682b17a1d466f2fcf"
|
||||
integrity sha512-fcqHEYe1MzAghU6/Hz86lHDlBNsA+lO48nAcm7/wA+kIzwS6uuJbUG33tBZjksj7GAZ1iUQ6NHwjUURPmSGang==
|
||||
react-dropzone@12.0.5:
|
||||
version "12.0.5"
|
||||
resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-12.0.5.tgz#f9b557484a8afd6267f670f96a770ddd3948838b"
|
||||
integrity sha512-zUjZigD0VJ91CSm9T1h7ErxFReBLaa9sjS2dUL0+inb0RROZpSJTNDHPY1rrBES5V2NXhF8v0kghmaHc81BMFg==
|
||||
dependencies:
|
||||
attr-accept "^2.2.2"
|
||||
file-selector "^0.4.0"
|
||||
|
@ -10857,10 +10911,10 @@ shallow-compare@^1.2.2:
|
|||
resolved "https://registry.yarnpkg.com/shallow-compare/-/shallow-compare-1.2.2.tgz#fa4794627bf455a47c4f56881d8a6132d581ffdb"
|
||||
integrity sha512-LUMFi+RppPlrHzbqmFnINTrazo0lPNwhcgzuAXVVcfy/mqPDrQmHAyz5bvV0gDAuRFrk804V0HpQ6u9sZ0tBeg==
|
||||
|
||||
sharp@^0.30.1:
|
||||
version "0.30.2"
|
||||
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.2.tgz#95b309b2740424702dc19b62a62595dd34a458b1"
|
||||
integrity sha512-mrMeKI5ECTdYhslPlA2TbBtU3nZXMEBcQwI6qYXjPlu1LpW4HBZLFm6xshMI1HpIdEEJ3UcYp5AKifLT/fEHZQ==
|
||||
sharp@^0.30.1, sharp@^0.30.3:
|
||||
version "0.30.3"
|
||||
resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.3.tgz#315a1817423a4d1cde5119a21c99c234a7a6fb37"
|
||||
integrity sha512-rjpfJFK58ZOFSG8sxYSo3/JQb4ej095HjXp9X7gVu7gEn1aqSG8TCW29h/Rr31+PXrFADo1H/vKfw0uhMQWFtg==
|
||||
dependencies:
|
||||
color "^4.2.1"
|
||||
detect-libc "^2.0.1"
|
||||
|
@ -11612,29 +11666,29 @@ table@^6.0.9:
|
|||
string-width "^4.2.3"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
tailwindcss@3.0.23:
|
||||
version "3.0.23"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.23.tgz#c620521d53a289650872a66adfcb4129d2200d10"
|
||||
integrity sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==
|
||||
tailwindcss@3.0.24:
|
||||
version "3.0.24"
|
||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.24.tgz#22e31e801a44a78a1d9a81ecc52e13b69d85704d"
|
||||
integrity sha512-H3uMmZNWzG6aqmg9q07ZIRNIawoiEcNFKDfL+YzOPuPsXuDXxJxB9icqzLgdzKNwjG3SAro2h9SYav8ewXNgig==
|
||||
dependencies:
|
||||
arg "^5.0.1"
|
||||
chalk "^4.1.2"
|
||||
chokidar "^3.5.3"
|
||||
color-name "^1.1.4"
|
||||
cosmiconfig "^7.0.1"
|
||||
detective "^5.2.0"
|
||||
didyoumean "^1.2.2"
|
||||
dlv "^1.1.3"
|
||||
fast-glob "^3.2.11"
|
||||
glob-parent "^6.0.2"
|
||||
is-glob "^4.0.3"
|
||||
lilconfig "^2.0.5"
|
||||
normalize-path "^3.0.0"
|
||||
object-hash "^2.2.0"
|
||||
postcss "^8.4.6"
|
||||
object-hash "^3.0.0"
|
||||
picocolors "^1.0.0"
|
||||
postcss "^8.4.12"
|
||||
postcss-js "^4.0.0"
|
||||
postcss-load-config "^3.1.0"
|
||||
postcss-load-config "^3.1.4"
|
||||
postcss-nested "5.0.6"
|
||||
postcss-selector-parser "^6.0.9"
|
||||
postcss-selector-parser "^6.0.10"
|
||||
postcss-value-parser "^4.2.0"
|
||||
quick-lru "^5.1.1"
|
||||
resolve "^1.22.0"
|
||||
|
|
|
@ -2,16 +2,22 @@
|
|||
|
||||
set -e # exit on first error
|
||||
|
||||
while getopts d:t: flag
|
||||
while getopts d:t:r: flag
|
||||
do
|
||||
case "${flag}" in
|
||||
d) delay=${OPTARG};;
|
||||
t) timeout=${OPTARG};;
|
||||
r) reason=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
delay=${delay:-0} # default to no delay
|
||||
timeout=${timeout:-300} # default timeout is 300s
|
||||
|
||||
if [[ -z $reason ]]; then
|
||||
echo "Please provide a reason for disabling the portal (use '-r <reason>')."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
countdown() {
|
||||
local secs=$1
|
||||
while [ $secs -gt 0 ]; do
|
||||
|
@ -24,8 +30,8 @@ countdown() {
|
|||
# delay disabling the portal
|
||||
countdown $delay
|
||||
|
||||
# stop healh-check so the server is taken our of load balancer
|
||||
docker exec health-check cli/disable
|
||||
# stop health-check so the server is taken our of load balancer
|
||||
docker exec health-check cli disable $reason
|
||||
|
||||
# then wait 5 minutes for the load balancer to propagate the dns records
|
||||
countdown $timeout
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
set -e # exit on first error
|
||||
|
||||
# start the health-checks service
|
||||
docker exec health-check cli/enable
|
||||
docker exec health-check cli enable
|
||||
|
|
Reference in New Issue