diff --git a/docker-compose.yml b/docker-compose.yml
index 9c0d59d5..af9b53a2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -64,8 +64,6 @@ services:
logging: *default-logging
env_file:
- .env
- environment:
- - SKYD_DISK_CACHE_ENABLED=${SKYD_DISK_CACHE_ENABLED:-true}
volumes:
- ./docker/data/nginx/cache:/data/nginx/cache
- ./docker/data/nginx/blocker:/data/nginx/blocker
diff --git a/docker/nginx/conf.d/include/cors-headers b/docker/nginx/conf.d/include/cors-headers
index 0f0bb328..f6a303cd 100644
--- a/docker/nginx/conf.d/include/cors-headers
+++ b/docker/nginx/conf.d/include/cors-headers
@@ -2,4 +2,4 @@ more_set_headers 'Access-Control-Allow-Origin: $http_origin';
more_set_headers 'Access-Control-Allow-Credentials: true';
more_set_headers 'Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE';
more_set_headers 'Access-Control-Allow-Headers: DNT,User-Agent,X-Requested-With,If-Modified-Since,If-None-Match,Cache-Control,Content-Type,Range,X-HTTP-Method-Override,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location,Skynet-API-Key';
-more_set_headers 'Access-Control-Expose-Headers: Content-Length,Content-Range,ETag,Skynet-File-Metadata,Skynet-Skylink,Skynet-Proof,Skynet-Portal-Api,Skynet-Server-Api,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location';
+more_set_headers 'Access-Control-Expose-Headers: Content-Length,Content-Range,ETag,Accept-Ranges,Skynet-File-Metadata,Skynet-Skylink,Skynet-Proof,Skynet-Portal-Api,Skynet-Server-Api,upload-offset,upload-metadata,upload-length,tus-version,tus-resumable,tus-extension,tus-max-size,upload-concat,location';
diff --git a/docker/nginx/conf.d/include/location-hns b/docker/nginx/conf.d/include/location-hns
index f41ac303..0ddb62d7 100644
--- a/docker/nginx/conf.d/include/location-hns
+++ b/docker/nginx/conf.d/include/location-hns
@@ -1,10 +1,8 @@
-include /etc/nginx/conf.d/include/proxy-buffer;
include /etc/nginx/conf.d/include/proxy-pass-internal;
include /etc/nginx/conf.d/include/portal-access-check;
# variable definititions - we need to define a variable to be able to access it in lua by ngx.var.something
-set $skylink ''; # placeholder for the base64 skylink
-set $skylink_base32 ''; # placeholder for the base32 skylink
+set $skylink ''; # placeholder for the raw 46 bit skylink
# resolve handshake domain by requesting to /hnsres endpoint and assign correct values to $skylink and $rest
rewrite_by_lua_block {
@@ -75,16 +73,10 @@ rewrite_by_lua_block {
if ngx.var.path == "/" and skylink_rest ~= nil and skylink_rest ~= "" and skylink_rest ~= "/" then
ngx.var.path = skylink_rest
end
-
- -- assign base32 skylink to be used in proxy_pass
- ngx.var.skylink_base32 = require("skynet.skylink").base32(ngx.var.skylink)
}
-# host header has to be adjusted to properly match server name
-proxy_set_header Host $skylink_base32.$skynet_portal_domain;
-
-# pass the skylink request to subdomain skylink server
-proxy_pass $scheme://$server_addr$path$is_args$args;
+# we proxy to another nginx location rather than directly to siad because we do not want to deal with caching here
+proxy_pass https://127.0.0.1/$skylink$path$is_args$args;
# in case siad returns location header, we need to replace the skylink with the domain name
header_filter_by_lua_block {
diff --git a/docker/nginx/conf.d/include/location-skylink b/docker/nginx/conf.d/include/location-skylink
index c613fe29..995a6e2d 100644
--- a/docker/nginx/conf.d/include/location-skylink
+++ b/docker/nginx/conf.d/include/location-skylink
@@ -1,70 +1,16 @@
include /etc/nginx/conf.d/include/cors;
-include /etc/nginx/conf.d/include/proxy-buffer;
-include /etc/nginx/conf.d/include/proxy-cache-downloads;
include /etc/nginx/conf.d/include/track-download;
limit_conn downloads_by_ip 100; # ddos protection: max 100 downloads at a time
# ensure that skylink that we pass around is base64 encoded (transform base32 encoded ones)
# this is important because we want only one format in cache keys and logs
-set_by_lua_block $skylink { return require("skynet.skylink").base64(ngx.var.skylink) }
-
-# $skylink_v1 and $skylink_v2 variables default to the same value but in case the requested skylink was:
-# a) skylink v1 - it would not matter, no additional logic is executed
-# b) skylink v2 - in a lua block below we will resolve the skylink v2 into skylink v1 and update
-# $skylink_v1 variable so then the proxy request to skyd can be cached in nginx (proxy_cache_key
-# in proxy-cache-downloads includes $skylink_v1 as a part of the cache key)
-set $skylink_v1 $skylink;
-set $skylink_v2 $skylink;
-
-# variable for Skynet-Proof header that we need to inject
-# into a response if the request was for skylink v2
-set $skynet_proof '';
+set_by_lua_block $skylink { return require("skynet.skylink").parse(ngx.var.skylink) }
# default download rate to unlimited
set $limit_rate 0;
access_by_lua_block {
- -- the block below only makes sense if we are using nginx cache
- if not ngx.var.skyd_disk_cache_enabled then
- local httpc = require("resty.http").new()
-
- -- detect whether requested skylink is v2
- local isBase32v2 = string.len(ngx.var.skylink) == 55 and string.sub(ngx.var.skylink, 0, 2) == "04"
- local isBase64v2 = string.len(ngx.var.skylink) == 46 and string.sub(ngx.var.skylink, 0, 2) == "AQ"
-
- if isBase32v2 or isBase64v2 then
- -- 10.10.10.10 points to sia service (alias not available when using resty-http)
- local res, err = httpc:request_uri("http://10.10.10.10:9980/skynet/resolve/" .. ngx.var.skylink_v2, {
- headers = { ["User-Agent"] = "Sia-Agent" }
- })
-
- -- print error and exit with 500 or exit with response if status is not 200
- if err or (res and res.status ~= ngx.HTTP_OK) then
- ngx.status = (err and ngx.HTTP_INTERNAL_SERVER_ERROR) or res.status
- ngx.header["content-type"] = "text/plain"
- ngx.say(err or res.body)
- return ngx.exit(ngx.status)
- end
-
- local json = require('cjson')
- local resolve = json.decode(res.body)
- ngx.var.skylink_v1 = resolve.skylink
- ngx.var.skynet_proof = res.headers["Skynet-Proof"]
- end
-
- -- check if skylink v1 is present on blocklist (compare hashes)
- if require("skynet.blocklist").is_blocked(ngx.var.skylink_v1) then
- return require("skynet.blocklist").exit_illegal()
- end
-
- -- if skylink is found on nocache list then set internal nocache variable
- -- to tell nginx that it should not try and cache this file (too large)
- if ngx.shared.nocache:get(ngx.var.skylink_v1) then
- ngx.var.nocache = "1"
- end
- end
-
if require("skynet.account").accounts_enabled() then
-- check if portal is in authenticated only mode
if require("skynet.account").is_access_unauthorized() then
@@ -84,36 +30,10 @@ access_by_lua_block {
end
}
-header_filter_by_lua_block {
- ngx.header["Skynet-Portal-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_portal_domain
- ngx.header["Skynet-Server-Api"] = ngx.var.scheme .. "://" .. ngx.var.skynet_server_domain
-
- -- the block below only makes sense if we are using nginx cache
- if not ngx.var.skyd_disk_cache_enabled then
- -- not empty skynet_proof means this is a skylink v2 request
- -- so we should replace the Skynet-Proof header with the one
- -- we got from /skynet/resolve/ endpoint, otherwise we would
- -- be serving cached empty v1 skylink Skynet-Proof header
- if ngx.var.skynet_proof and ngx.var.skynet_proof ~= "" then
- ngx.header["Skynet-Proof"] = ngx.var.skynet_proof
- end
-
- -- add skylink to nocache list if it exceeds 1GB (1e+9 bytes) threshold
- -- (content length can be nil for already cached files - we can ignore them)
- if ngx.header["Content-Length"] and tonumber(ngx.header["Content-Length"]) > 1e+9 then
- ngx.shared.nocache:set(ngx.var.skylink_v1, ngx.header["Content-Length"])
- end
- end
-}
-
limit_rate_after 512k;
limit_rate $limit_rate;
proxy_read_timeout 600;
proxy_set_header User-Agent: Sia-Agent;
-# in case the requested skylink was v2 and we already resolved it to skylink v1, we are going to pass resolved
-# skylink v1 to skyd to save that extra skylink v2 lookup in skyd but in turn, in case skyd returns a redirect
-# we need to rewrite the skylink v1 to skylink v2 in the location header with proxy_redirect
-proxy_redirect $skylink_v1 $skylink_v2;
-proxy_pass http://sia:9980/skynet/skylink/$skylink_v1$path$is_args$args;
+proxy_pass http://sia:9980/skynet/skylink/$skylink$path$is_args$args;
diff --git a/docker/nginx/conf.d/include/proxy-buffer b/docker/nginx/conf.d/include/proxy-buffer
deleted file mode 100644
index 0fafbade..00000000
--- a/docker/nginx/conf.d/include/proxy-buffer
+++ /dev/null
@@ -1,5 +0,0 @@
-# if you are expecting large headers (ie. Skynet-Skyfile-Metadata), tune these values to your needs
-# read more: https://www.getpagespeed.com/server-setup/nginx/tuning-proxy_buffer_size-in-nginx
-proxy_buffer_size 4096k;
-proxy_buffers 64 256k;
-proxy_busy_buffers_size 4096k; # at least as high as proxy_buffer_size
diff --git a/docker/nginx/conf.d/include/proxy-cache-downloads b/docker/nginx/conf.d/include/proxy-cache-downloads
deleted file mode 100644
index 85aeeb9e..00000000
--- a/docker/nginx/conf.d/include/proxy-cache-downloads
+++ /dev/null
@@ -1,21 +0,0 @@
-proxy_cache skynet; # cache name
-proxy_cache_key $skylink_v1$path$arg_format$arg_attachment$arg_start$arg_end$http_range; # unique cache key
-proxy_cache_min_uses 3; # cache after 3 uses
-proxy_cache_valid 200 206 307 308 48h; # keep 200, 206, 307 and 308 responses valid for up to 2 days
-add_header X-Proxy-Cache $upstream_cache_status; # add response header to indicate cache hits and misses
-
-# map skyd env variable value to "1" for true and "0" for false (expected by proxy_no_cache)
-set_by_lua_block $skyd_disk_cache_enabled {
- return os.getenv("SKYD_DISK_CACHE_ENABLED") == "true" and "1" or "0"
-}
-
-# bypass - this will bypass cache hit on request (status BYPASS)
-# but still stores file in cache if cache conditions are met
-proxy_cache_bypass $cookie_nocache $arg_nocache $skyd_disk_cache_enabled;
-
-# no cache - this will ignore cache on request (status MISS)
-# and does not store file in cache under no condition
-set_if_empty $nocache "0";
-
-# disable cache when nocache is set or skyd cache is enabled
-proxy_no_cache $nocache $skyd_disk_cache_enabled;
diff --git a/docker/nginx/conf.d/server.local.conf b/docker/nginx/conf.d/server.local.conf
deleted file mode 100644
index 8a487a53..00000000
--- a/docker/nginx/conf.d/server.local.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-server {
- # local server - do not expose this port externally
- listen 8000;
-
- # secure traffic by limiting to only local networks
- include /etc/nginx/conf.d/include/local-network-only;
-
- include /etc/nginx/conf.d/server/server.local;
-}
diff --git a/docker/nginx/conf.d/server/server.api b/docker/nginx/conf.d/server/server.api
index 4f8f2512..7f2e714a 100644
--- a/docker/nginx/conf.d/server/server.api
+++ b/docker/nginx/conf.d/server/server.api
@@ -47,7 +47,9 @@ location /skynet/portal/blocklist {
proxy_cache skynet;
proxy_cache_valid 200 204 15m; # cache portal blocklist for 15 minutes
- proxy_pass http://blocker:4000/blocklist;
+
+ # 10.10.10.110 points to blocker service
+ proxy_pass http://10.10.10.110:4000/blocklist;
}
location /skynet/portals {
@@ -123,14 +125,15 @@ location /abuse/report {
location /hns {
include /etc/nginx/conf.d/include/cors;
- rewrite_by_lua_block {
- local hns_domain = string.match(ngx.var.uri, "/hns/([^/?]+)")
- local path = string.match(ngx.var.uri, "/hns/[^/?]+(.*)")
- local args = ngx.var.args and ngx.var.is_args .. ngx.var.args or ""
- local hns_subdomain_url = ngx.var.scheme .. "://" .. hns_domain .. ".hns." .. ngx.var.skynet_portal_domain .. path .. args
+ # match the request_uri and extract the hns domain and anything that is passed in the uri after it
+ # example: /hns/something/foo/bar matches:
+ # > hns_domain: something
+ # > path: /foo/bar/
+ set_by_lua_block $hns_domain { return string.match(ngx.var.uri, "/hns/([^/?]+)") }
+ set_by_lua_block $path { return string.match(ngx.var.uri, "/hns/[^/?]+(.*)") }
- return ngx.redirect(hns_subdomain_url, ngx.HTTP_MOVED_PERMANENTLY)
- }
+ proxy_set_header Host $host;
+ include /etc/nginx/conf.d/include/location-hns;
}
location /hnsres {
@@ -176,13 +179,13 @@ location /skynet/registry/subscription {
local httpc = require("resty.http").new()
-- fetch account limits and set download bandwidth and registry delays accordingly
- local res, err = httpc:request_uri("http://10.10.10.70:3000/user/limits", {
+ local res, err = httpc:request_uri("http://10.10.10.70:3000/user/limits?unit=byte", {
headers = { ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt }
})
-- fail gracefully in case /user/limits failed
if err or (res and res.status ~= ngx.HTTP_OK) then
- ngx.log(ngx.ERR, "Failed accounts service request /user/limits: ", err or ("[HTTP " .. res.status .. "] " .. res.body))
+ ngx.log(ngx.ERR, "Failed accounts service request /user/limits?unit=byte: ", err or ("[HTTP " .. res.status .. "] " .. res.body))
elseif res and res.status == ngx.HTTP_OK then
local json = require('cjson')
local limits = json.decode(res.body)
@@ -266,10 +269,10 @@ location /skynet/tus {
if require("skynet.account").is_access_forbidden() then
return require("skynet.account").exit_access_forbidden()
end
-
+
-- get account limits of currently authenticated user
local limits = require("skynet.account").get_account_limits()
-
+
-- apply upload size limits
ngx.req.set_header("SkynetMaxUploadSize", limits.maxUploadSize)
end
@@ -337,14 +340,7 @@ location ~ "^/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" {
set $skylink $2;
set $path $3;
- rewrite_by_lua_block {
- local skynet_skylink = require("skynet.skylink")
- local base32_skylink = skynet_skylink.base32(ngx.var.skylink)
- local args = ngx.var.args and ngx.var.is_args .. ngx.var.args or ""
- local base32_url = ngx.var.scheme .. "://" .. base32_skylink .. "." .. ngx.var.skynet_portal_domain .. ngx.var.path .. args
-
- return ngx.redirect(base32_url, ngx.HTTP_MOVED_PERMANENTLY)
- }
+ include /etc/nginx/conf.d/include/location-skylink;
}
location ~ "^/file/(([a-zA-Z0-9-_]{46}|[a-z0-9]{55})(/.*)?)$" {
@@ -358,7 +354,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/proxy-buffer;
include /etc/nginx/conf.d/include/track-download;
limit_conn downloads_by_ip 100; # ddos protection: max 100 downloads at a time
diff --git a/docker/nginx/conf.d/server/server.dnslink b/docker/nginx/conf.d/server/server.dnslink
index 22ce75a3..cf385a1d 100644
--- a/docker/nginx/conf.d/server/server.dnslink
+++ b/docker/nginx/conf.d/server/server.dnslink
@@ -37,9 +37,7 @@ location / {
ngx.var.skylink = cache_value
end
- ngx.var.skylink = require("skynet.skylink").base64(ngx.var.skylink)
- ngx.var.skylink_v1 = ngx.var.skylink
- ngx.var.skylink_v2 = ngx.var.skylink
+ ngx.var.skylink = require("skynet.skylink").parse(ngx.var.skylink)
}
include /etc/nginx/conf.d/include/location-skylink;
diff --git a/docker/nginx/conf.d/server/server.local b/docker/nginx/conf.d/server/server.local
deleted file mode 100644
index 3a8ac118..00000000
--- a/docker/nginx/conf.d/server/server.local
+++ /dev/null
@@ -1,37 +0,0 @@
-include /etc/nginx/conf.d/include/init-optional-variables;
-
-location /skynet/blocklist {
- client_max_body_size 10m; # increase max body size to account for large lists
- client_body_buffer_size 10m; # force whole body to memory so we can read it
-
- content_by_lua_block {
- local httpc = require("resty.http").new()
-
- ngx.req.read_body() -- ensure the post body data is read before using get_body_data
-
- -- proxy blocklist update request
- -- 10.10.10.10 points to sia service (alias not available when using resty-http)
- local res, err = httpc:request_uri("http://10.10.10.10:9980/skynet/blocklist", {
- method = "POST",
- body = ngx.req.get_body_data(),
- headers = {
- ["Content-Type"] = "application/x-www-form-urlencoded",
- ["Authorization"] = require("skynet.utils").authorization_header(),
- ["User-Agent"] = "Sia-Agent",
- }
- })
-
- -- print error and exit with 500 or exit with response if status is not 204
- if err or (res and res.status ~= ngx.HTTP_NO_CONTENT) then
- ngx.status = (err and ngx.HTTP_INTERNAL_SERVER_ERROR) or res.status
- ngx.header["content-type"] = "text/plain"
- ngx.say(err or res.body)
- return ngx.exit(ngx.status)
- end
-
- require("skynet.blocklist").reload()
-
- ngx.status = ngx.HTTP_NO_CONTENT
- return ngx.exit(ngx.status)
- }
-}
diff --git a/docker/nginx/libs/skynet/account.lua b/docker/nginx/libs/skynet/account.lua
index e7ff9033..7f000d6e 100644
--- a/docker/nginx/libs/skynet/account.lua
+++ b/docker/nginx/libs/skynet/account.lua
@@ -43,15 +43,15 @@ function _M.get_account_limits()
if ngx.var.account_limits == "" then
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/user/limits", {
+ local res, err = httpc:request_uri("http://10.10.10.70:3000/user/limits?unit=byte", {
headers = { ["Cookie"] = "skynet-jwt=" .. ngx.var.skynet_jwt }
})
-
+
-- fail gracefully in case /user/limits failed
if err or (res and res.status ~= ngx.HTTP_OK) then
- ngx.log(ngx.ERR, "Failed accounts service request /user/limits: ", err or ("[HTTP " .. res.status .. "] " .. res.body))
+ ngx.log(ngx.ERR, "Failed accounts service request /user/limits?unit=byte: ", err or ("[HTTP " .. res.status .. "] " .. res.body))
ngx.var.account_limits = cjson.encode(anon_limits)
elseif res and res.status == ngx.HTTP_OK then
ngx.var.account_limits = res.body
diff --git a/docker/nginx/libs/skynet/blocklist.lua b/docker/nginx/libs/skynet/blocklist.lua
deleted file mode 100644
index 29f53032..00000000
--- a/docker/nginx/libs/skynet/blocklist.lua
+++ /dev/null
@@ -1,66 +0,0 @@
-local _M = {}
-
-function _M.reload()
- local httpc = require("resty.http").new()
-
- -- fetch blocklist records (all blocked skylink hashes)
- -- 10.10.10.10 points to sia service (alias not available when using resty-http)
- local res, err = httpc:request_uri("http://10.10.10.10:9980/skynet/blocklist", {
- headers = {
- ["User-Agent"] = "Sia-Agent",
- }
- })
-
- -- fail whole request in case this request failed, we want to make sure
- -- the blocklist is pre cached before serving first skylink
- if err or (res and res.status ~= ngx.HTTP_OK) then
- ngx.log(ngx.ERR, "Failed skyd service request /skynet/blocklist: ", err or ("[HTTP " .. res.status .. "] " .. res.body))
- ngx.status = (err and ngx.HTTP_INTERNAL_SERVER_ERROR) or res.status
- ngx.header["content-type"] = "text/plain"
- ngx.say(err or res.body)
- return ngx.exit(ngx.status)
- elseif res and res.status == ngx.HTTP_OK then
- local json = require('cjson')
- local data = json.decode(res.body)
-
- -- mark all existing entries as expired
- ngx.shared.blocklist:flush_all()
-
- -- check if blocklist is table (it is null when empty)
- if type(data.blocklist) == "table" then
- -- set all cache entries one by one (resets expiration)
- for i, hash in ipairs(data.blocklist) do
- ngx.shared.blocklist:set(hash, true)
- end
- end
-
- -- ensure that init flag is persisted
- ngx.shared.blocklist:set("__init", true)
-
- -- remove all leftover expired entries
- ngx.shared.blocklist:flush_expired()
- end
-end
-
-function _M.is_blocked(skylink)
- -- make sure that blocklist has been preloaded
- if not ngx.shared.blocklist:get("__init") then _M.reload() end
-
- -- hash skylink before comparing it with blocklist
- local hash = require("skynet.skylink").hash(skylink)
-
- -- we need to use get_stale because we are expiring previous
- -- entries when the blocklist is reloading and we still want
- -- to block them until the reloading is finished
- return ngx.shared.blocklist:get_stale(hash) == true
-end
-
--- exit with 416 illegal content status code
-function _M.exit_illegal()
- ngx.status = ngx.HTTP_ILLEGAL
- ngx.header["content-type"] = "text/plain"
- ngx.say("Unavailable For Legal Reasons")
- return ngx.exit(ngx.status)
-end
-
-return _M
diff --git a/docker/nginx/libs/skynet/skylink.lua b/docker/nginx/libs/skynet/skylink.lua
index c6372a41..adcf0b70 100644
--- a/docker/nginx/libs/skynet/skylink.lua
+++ b/docker/nginx/libs/skynet/skylink.lua
@@ -3,13 +3,10 @@ local _M = {}
local basexx = require("basexx")
local hasher = require("hasher")
--- use lowercase alphabet since our skylinks are part of urls
-local base32_alphabet = "0123456789abcdefghijklmnopqrstuv"
-
-- parse any skylink and return base64 version
-function _M.base64(skylink)
+function _M.parse(skylink)
if string.len(skylink) == 55 then
- local decoded = basexx.from_basexx(string.lower(skylink), base32_alphabet, 5)
+ local decoded = basexx.from_basexx(string.upper(skylink), "0123456789ABCDEFGHIJKLMNOPQRSTUV", 5)
return basexx.to_url64(decoded)
end
@@ -17,21 +14,10 @@ function _M.base64(skylink)
return skylink
end
--- parse any skylink and return base32 version
-function _M.base32(skylink)
- if string.len(skylink) == 46 then
- local decoded = basexx.from_url64(skylink)
-
- return basexx.to_basexx(decoded, base32_alphabet, 5)
- end
-
- return skylink
-end
-
-- hash skylink into 32 bytes hash used in blocklist
function _M.hash(skylink)
-- ensure that the skylink is base64 encoded
- local base64Skylink = _M.base64(skylink)
+ local base64Skylink = _M.parse(skylink)
-- decode skylink from base64 encoding
local rawSkylink = basexx.from_url64(base64Skylink)
diff --git a/docker/nginx/libs/skynet/skylink.spec.lua b/docker/nginx/libs/skynet/skylink.spec.lua
index 9949e534..0502a833 100644
--- a/docker/nginx/libs/skynet/skylink.spec.lua
+++ b/docker/nginx/libs/skynet/skylink.spec.lua
@@ -1,28 +1,15 @@
local skynet_skylink = require("skynet.skylink")
-describe("base64", function()
+describe("parse", function()
local base32 = "0404dsjvti046fsua4ktor9grrpe76erq9jot9cvopbhsvsu76r4r30"
local base64 = "AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA"
it("should return unchanged base64 skylink", function()
- assert.is.same(skynet_skylink.base64(base64), base64)
+ assert.is.same(skynet_skylink.parse(base64), base64)
end)
it("should transform base32 skylink into base64", function()
- assert.is.same(skynet_skylink.base64(base32), base64)
- end)
-end)
-
-describe("base32", function()
- local base32 = "0404dsjvti046fsua4ktor9grrpe76erq9jot9cvopbhsvsu76r4r30"
- local base64 = "AQBG8n_sgEM_nlEp3G0w3vLjmdvSZ46ln8ZXHn-eObZNjA"
-
- it("should return unchanged base32 skylink", function()
- assert.is.same(skynet_skylink.base32(base32), base32)
- end)
-
- it("should transform base64 skylink into base32", function()
- assert.is.same(skynet_skylink.base32(base64), base32)
+ assert.is.same(skynet_skylink.parse(base32), base64)
end)
end)
diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf
index 7f43d192..95bb61ac 100644
--- a/docker/nginx/nginx.conf
+++ b/docker/nginx/nginx.conf
@@ -31,7 +31,6 @@ env SERVER_DOMAIN;
env PORTAL_MODULES;
env ACCOUNTS_LIMIT_ACCESS;
env SIA_API_PASSWORD;
-env SKYD_DISK_CACHE_ENABLED;
events {
worker_connections 8192;
@@ -75,20 +74,10 @@ http {
# proxy cache definition
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=skynet:10m max_size=50g min_free=100g inactive=48h use_temp_path=off;
- # create a shared blocklist dictionary with size of 30 megabytes
- # estimated capacity of 1 megabyte dictionary is 3500 blocklist entries
- # that gives us capacity of around 100k entries in 30 megabyte dictionary
- lua_shared_dict blocklist 30m;
-
- # create a shared dictionary to fill with skylinks that should not
- # be cached due to the large size or some other reasons
- lua_shared_dict nocache 10m;
-
# this runs before forking out nginx worker processes
init_by_lua_block {
require "cjson"
require "resty.http"
- require "skynet.blocklist"
require "skynet.skylink"
require "skynet.utils"
}
diff --git a/packages/dashboard-v2/src/components/AvatarUploader/AvatarUploader.js b/packages/dashboard-v2/src/components/AvatarUploader/AvatarUploader.js
new file mode 100644
index 00000000..9f5bbc82
--- /dev/null
+++ b/packages/dashboard-v2/src/components/AvatarUploader/AvatarUploader.js
@@ -0,0 +1,35 @@
+import { useEffect, useState } from "react";
+
+import { useUser } from "../../contexts/user";
+import { SimpleUploadIcon } from "../Icons";
+
+const AVATAR_PLACEHOLDER = "/images/avatar-placeholder.svg";
+
+export const AvatarUploader = (props) => {
+ const { user } = useUser();
+ const [imageUrl, setImageUrl] = useState(AVATAR_PLACEHOLDER);
+
+ useEffect(() => {
+ setImageUrl(user.avatarUrl ?? AVATAR_PLACEHOLDER);
+ }, [user]);
+
+ return (
+
+
+
+
+
+
+ Upload profile picture
+
+ {/* TODO: actual uploading */}
+
+
+ );
+};
diff --git a/packages/dashboard-v2/src/components/AvatarUploader/index.js b/packages/dashboard-v2/src/components/AvatarUploader/index.js
new file mode 100644
index 00000000..74358cdc
--- /dev/null
+++ b/packages/dashboard-v2/src/components/AvatarUploader/index.js
@@ -0,0 +1 @@
+export * from "./AvatarUploader";
diff --git a/packages/dashboard-v2/src/components/Button/Button.js b/packages/dashboard-v2/src/components/Button/Button.js
index 95ecbede..40ec1ad9 100644
--- a/packages/dashboard-v2/src/components/Button/Button.js
+++ b/packages/dashboard-v2/src/components/Button/Button.js
@@ -6,7 +6,7 @@ import styled from "styled-components";
*/
export const Button = styled.button.attrs(({ $primary }) => ({
type: "button",
- className: `px-6 py-3 rounded-full font-sans uppercase text-xs tracking-wide text-palette-600 transition-[filter] hover:brightness-90
+ className: `px-6 py-2.5 rounded-full font-sans uppercase text-xs tracking-wide text-palette-600 transition-[filter] hover:brightness-90
${$primary ? "bg-primary" : "bg-white border-2 border-black"}`,
}))``;
Button.propTypes = {
diff --git a/packages/dashboard-v2/src/components/CopyButton.js b/packages/dashboard-v2/src/components/CopyButton.js
new file mode 100644
index 00000000..479352d2
--- /dev/null
+++ b/packages/dashboard-v2/src/components/CopyButton.js
@@ -0,0 +1,50 @@
+import { useCallback, useRef, useState } from "react";
+import copy from "copy-text-to-clipboard";
+import styled from "styled-components";
+import { useClickAway } from "react-use";
+
+import { CopyIcon } from "./Icons";
+
+const Button = styled.button.attrs({
+ className: "relative inline-flex items-center hover:text-primary",
+})``;
+
+const TooltipContainer = styled.div.attrs(({ $visible }) => ({
+ className: `absolute left-full top-1/2 z-10
+ bg-white rounded border border-primary/30 shadow-md
+ pointer-events-none transition-opacity duration-150 ease-in-out
+ ${$visible ? "opacity-100" : "opacity-0"}`,
+}))`
+ transform: translateY(-50%);
+`;
+
+const TooltipContent = styled.div.attrs({
+ className: "bg-primary-light/10 text-palette-600 py-2 px-4 ",
+})``;
+
+export const CopyButton = ({ value, className }) => {
+ const containerRef = useRef();
+ const [copied, setCopied] = useState(false);
+ const [timer, setTimer] = useState(null);
+
+ const handleCopy = useCallback(() => {
+ clearTimeout(timer);
+ copy(value);
+ setCopied(true);
+
+ setTimer(setTimeout(() => setCopied(false), 1500));
+ }, [value, timer]);
+
+ useClickAway(containerRef, () => setCopied(false));
+
+ return (
+
+
+
+
+
+ Copied to clipboard
+
+
+ );
+};
diff --git a/packages/dashboard-v2/src/components/CurrentPlan/CurrentPlan.js b/packages/dashboard-v2/src/components/CurrentPlan/CurrentPlan.js
index c5cdee36..f8a5cf9e 100644
--- a/packages/dashboard-v2/src/components/CurrentPlan/CurrentPlan.js
+++ b/packages/dashboard-v2/src/components/CurrentPlan/CurrentPlan.js
@@ -2,7 +2,8 @@ import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { useUser } from "../../contexts/user";
-import useSubscriptionPlans from "../../hooks/useSubscriptionPlans";
+import useActivePlan from "../../hooks/useActivePlan";
+import { ContainerLoadingIndicator } from "../LoadingIndicator";
import LatestPayment from "./LatestPayment";
import SuggestedPlan from "./SuggestedPlan";
@@ -11,13 +12,10 @@ dayjs.extend(relativeTime);
const CurrentPlan = () => {
const { user, error: userError } = useUser();
- const { activePlan, plans, error: plansError } = useSubscriptionPlans(user);
+ const { plans, activePlan, error: plansError } = useActivePlan(user);
if (!user || !activePlan) {
- return (
- // TODO: a nicer loading indicator
- Loading...
- );
+ return ;
}
if (userError || plansError) {
diff --git a/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js b/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js
index b467e1ea..44be79ed 100644
--- a/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js
+++ b/packages/dashboard-v2/src/components/CurrentUsage/CurrentUsage.js
@@ -1,24 +1,50 @@
-import * as React from "react";
+import { useEffect, useMemo, useState } from "react";
import fileSize from "pretty-bytes";
import { Link } from "gatsby";
+import useSWR from "swr";
+
+import { useUser } from "../../contexts/user";
+import useActivePlan from "../../hooks/useActivePlan";
+import { ContainerLoadingIndicator } from "../LoadingIndicator";
import { GraphBar } from "./GraphBar";
import { UsageGraph } from "./UsageGraph";
-// TODO: get real data
-const useUsageData = () => ({
- files: {
- used: 19_521,
- limit: 20_000,
- },
- storage: {
- used: 23_000_000_000,
- limit: 1_000_000_000_000,
- },
-});
+const useUsageData = () => {
+ const { user } = useUser();
+ const { activePlan, error } = useActivePlan(user);
+ const { data: stats, error: statsError } = useSWR("user/stats");
+
+ const [loading, setLoading] = useState(true);
+ const [usage, setUsage] = useState({});
+
+ const hasError = error || statsError;
+ const hasData = activePlan && stats;
+
+ useEffect(() => {
+ if (hasData || hasError) {
+ setLoading(false);
+ }
+
+ if (hasData && !hasError) {
+ setUsage({
+ filesUsed: stats?.numUploads,
+ filesLimit: activePlan?.limits?.maxNumberUploads,
+ storageUsed: stats?.totalUploadsSize,
+ storageLimit: activePlan?.limits?.storageLimit,
+ });
+ }
+ }, [hasData, hasError, stats, activePlan]);
+
+ return {
+ error: error || statsError,
+ loading,
+ usage,
+ };
+};
const size = (bytes) => {
- const text = fileSize(bytes, { maximumFractionDigits: 1 });
+ const text = fileSize(bytes ?? 0, { maximumFractionDigits: 0 });
const [value, unit] = text.split(" ");
return {
@@ -28,12 +54,26 @@ const size = (bytes) => {
};
};
-export default function CurrentUsage() {
- const { files, storage } = useUsageData();
+const ErrorMessage = () => (
+
+
We were not able to fetch the current usage data.
+
We'll try again automatically.
+
+);
- const storageUsage = size(storage.used);
- const storageLimit = size(storage.limit);
- const filesUsedLabel = React.useMemo(() => ({ value: files.used, unit: "files" }), [files.used]);
+export default function CurrentUsage() {
+ const { usage, error, loading } = useUsageData();
+ const storageUsage = size(usage.storageUsed);
+ const storageLimit = size(usage.storageLimit);
+ const filesUsedLabel = useMemo(() => ({ value: usage.filesUsed, unit: "files" }), [usage.filesUsed]);
+
+ if (loading) {
+ return ;
+ }
+
+ if (error) {
+ return ;
+ }
return (
<>
@@ -41,7 +81,7 @@ export default function CurrentUsage() {
{storageUsage.text} of {storageLimit.text}
- {files.used} of {files.limit} files
+ {usage.filesUsed} of {usage.filesLimit} files
@@ -49,8 +89,8 @@ export default function CurrentUsage() {
{storageLimit.text}
-
-
+
+
Files
@@ -62,7 +102,7 @@ export default function CurrentUsage() {
UPGRADE
{" "}
{/* TODO: proper URL */}
- {files.limit}
+ {usage.filesLimit}
diff --git a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.js b/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.js
index 461a9b7e..86cbad5f 100644
--- a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.js
+++ b/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenu.js
@@ -30,7 +30,7 @@ const TriggerIcon = styled(ChevronDownIcon).attrs({
`;
const Flyout = styled.div.attrs(({ open }) => ({
- className: `absolute top-full right-0 p-0
+ className: `absolute top-full right-0 p-0 z-10
border rounded border-palette-100
bg-white shadow-md shadow-palette-200/50
${open ? "visible" : "invisible"}`,
diff --git a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenuLink.js b/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenuLink.js
index 45a86483..426501c4 100644
--- a/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenuLink.js
+++ b/packages/dashboard-v2/src/components/DropdownMenu/DropdownMenuLink.js
@@ -3,18 +3,16 @@ import PropTypes from "prop-types";
const DropdownLink = styled.a.attrs({
className: `m-0 border-t border-palette-200/50 h-[60px]
- whitespace-nowrap transition-colors text-current
+ whitespace-nowrap transition-colors
hover:bg-palette-100/50 flex items-center
pr-8 pl-6 py-4 gap-4 first:border-0`,
})``;
export const DropdownMenuLink = ({ active, icon: Icon, label, ...props }) => (
- <>
-
- {Icon ? : null}
- {label}
-
- >
+
+ {Icon ? : null}
+ {label}
+
);
DropdownMenuLink.propTypes = {
diff --git a/packages/dashboard-v2/src/components/FileList/FileList.js b/packages/dashboard-v2/src/components/FileList/FileList.js
new file mode 100644
index 00000000..6342b970
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/FileList.js
@@ -0,0 +1,74 @@
+import * as React from "react";
+import useSWR from "swr";
+import { useMedia } from "react-use";
+
+import theme from "../../lib/theme";
+
+import { ContainerLoadingIndicator } from "../LoadingIndicator";
+import { Select, SelectOption } from "../Select";
+import { Switch } from "../Switch";
+import { TextInputIcon } from "../TextInputIcon/TextInputIcon";
+import { SearchIcon } from "../Icons";
+
+import FileTable from "./FileTable";
+import useFormattedFilesData from "./useFormattedFilesData";
+
+const FileList = ({ type }) => {
+ const isMediumScreenOrLarger = useMedia(`(min-width: ${theme.screens.md})`);
+ const { data, error } = useSWR(`user/${type}?pageSize=10`);
+ const items = useFormattedFilesData(data?.items || []);
+
+ const setFilter = (name, value) => console.log("filter", name, "set to", value);
+
+ if (!items.length) {
+ return (
+
+ {/* TODO: proper error message */}
+ {!data && !error &&
}
+ {!data && error &&
An error occurred while loading this data.
}
+ {data &&
No {type} found.
}
+
+ );
+ }
+
+ return (
+
+
+
}
+ onChange={console.log.bind(console)}
+ />
+
+
setFilter("showSmallFiles", value)} className="mr-8">
+
+ Show small files
+
+
+
+ File type:
+ setFilter("type", value)}>
+
+
+
+
+
+
+ Sort:
+ setFilter("type", value)}>
+
+
+
+
+
+
+
+
+ {/* TODO: mobile view (it's not tabular) */}
+ {isMediumScreenOrLarger ?
: "Mobile view"}
+
+ );
+};
+
+export default FileList;
diff --git a/packages/dashboard-v2/src/components/FileList/FileTable.js b/packages/dashboard-v2/src/components/FileList/FileTable.js
new file mode 100644
index 00000000..90c9600f
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/FileTable.js
@@ -0,0 +1,111 @@
+import { CogIcon, ShareIcon } from "../Icons";
+import { PopoverMenu } from "../PopoverMenu/PopoverMenu";
+import { Table, TableBody, TableCell, TableHead, TableHeadCell, TableRow } from "../Table";
+import { CopyButton } from "../CopyButton";
+
+const buildShareMenu = (item) => {
+ return [
+ {
+ label: "Facebook",
+ callback: () => {
+ console.info("share to Facebook", item);
+ },
+ },
+ {
+ label: "Twitter",
+ callback: () => {
+ console.info("share to Twitter", item);
+ },
+ },
+ {
+ label: "Discord",
+ callback: () => {
+ console.info("share to Discord", item);
+ },
+ },
+ ];
+};
+
+const buildOptionsMenu = (item) => {
+ return [
+ {
+ label: "Preview",
+ callback: () => {
+ console.info("preview", item);
+ },
+ },
+ {
+ label: "Download",
+ callback: () => {
+ console.info("download", item);
+ },
+ },
+ {
+ label: "Unpin",
+ callback: () => {
+ console.info("unpin", item);
+ },
+ },
+ {
+ label: "Report",
+ callback: () => {
+ console.info("report", item);
+ },
+ },
+ ];
+};
+
+export default function FileTable({ items }) {
+ return (
+
+
+
+ Name
+ Type
+
+ Size
+
+ Uploaded
+ Skylink
+ Activity
+
+
+
+ {items.map((item) => {
+ const { id, name, type, size, date, skylink } = item;
+
+ return (
+
+ {name}
+ {type}
+
+ {size}
+
+ {date}
+
+
+
+ {skylink}
+
+
+
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/packages/dashboard-v2/src/components/FileList/index.js b/packages/dashboard-v2/src/components/FileList/index.js
new file mode 100644
index 00000000..93296508
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/index.js
@@ -0,0 +1 @@
+export * from "./FileList";
diff --git a/packages/dashboard-v2/src/components/FileList/useFormattedFilesData.js b/packages/dashboard-v2/src/components/FileList/useFormattedFilesData.js
new file mode 100644
index 00000000..82d95090
--- /dev/null
+++ b/packages/dashboard-v2/src/components/FileList/useFormattedFilesData.js
@@ -0,0 +1,26 @@
+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 useFormattedFilesData = (items) => useMemo(() => items.map(formatItem), [items]);
+
+export default useFormattedFilesData;
diff --git a/packages/dashboard-v2/src/components/Icons/icons/CopyIcon.js b/packages/dashboard-v2/src/components/Icons/icons/CopyIcon.js
new file mode 100644
index 00000000..c3ceb9ac
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Icons/icons/CopyIcon.js
@@ -0,0 +1,10 @@
+import { withIconProps } from "../withIconProps";
+
+export const CopyIcon = withIconProps(({ size, ...props }) => (
+
+
+
+));
diff --git a/packages/dashboard-v2/src/components/Icons/icons/SearchIcon.js b/packages/dashboard-v2/src/components/Icons/icons/SearchIcon.js
new file mode 100644
index 00000000..f551dea6
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Icons/icons/SearchIcon.js
@@ -0,0 +1,10 @@
+import { withIconProps } from "../withIconProps";
+
+export const SearchIcon = withIconProps(({ size, ...props }) => (
+
+
+
+));
diff --git a/packages/dashboard-v2/src/components/Icons/icons/ShareIcon.js b/packages/dashboard-v2/src/components/Icons/icons/ShareIcon.js
new file mode 100644
index 00000000..f25afeaf
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Icons/icons/ShareIcon.js
@@ -0,0 +1,18 @@
+import { withIconProps } from "../withIconProps";
+
+export const ShareIcon = withIconProps(({ size, ...props }) => (
+
+
+
+));
diff --git a/packages/dashboard-v2/src/components/Icons/icons/SimpleUploadIcon.js b/packages/dashboard-v2/src/components/Icons/icons/SimpleUploadIcon.js
new file mode 100644
index 00000000..0406f012
--- /dev/null
+++ b/packages/dashboard-v2/src/components/Icons/icons/SimpleUploadIcon.js
@@ -0,0 +1,10 @@
+import { withIconProps } from "../withIconProps";
+
+export const SimpleUploadIcon = withIconProps(({ size, ...props }) => (
+
+
+
+));
diff --git a/packages/dashboard-v2/src/components/Icons/index.js b/packages/dashboard-v2/src/components/Icons/index.js
index 41552e34..611acef4 100644
--- a/packages/dashboard-v2/src/components/Icons/index.js
+++ b/packages/dashboard-v2/src/components/Icons/index.js
@@ -9,3 +9,7 @@ export * from "./icons/CircledErrorIcon";
export * from "./icons/CircledProgressIcon";
export * from "./icons/CircledArrowUpIcon";
export * from "./icons/PlusIcon";
+export * from "./icons/SearchIcon";
+export * from "./icons/CopyIcon";
+export * from "./icons/ShareIcon";
+export * from "./icons/SimpleUploadIcon";
diff --git a/packages/dashboard-v2/src/components/Icons/withIconProps.js b/packages/dashboard-v2/src/components/Icons/withIconProps.js
index d4267318..5da47331 100644
--- a/packages/dashboard-v2/src/components/Icons/withIconProps.js
+++ b/packages/dashboard-v2/src/components/Icons/withIconProps.js
@@ -4,7 +4,7 @@ const propTypes = {
/**
* Size of the icon's bounding box.
*/
- size: PropTypes.number,
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
};
const defaultProps = {
diff --git a/packages/dashboard-v2/src/components/LatestActivity/ActivityTable.js b/packages/dashboard-v2/src/components/LatestActivity/ActivityTable.js
index 647f9bf8..345a2daa 100644
--- a/packages/dashboard-v2/src/components/LatestActivity/ActivityTable.js
+++ b/packages/dashboard-v2/src/components/LatestActivity/ActivityTable.js
@@ -2,6 +2,7 @@ import * as React from "react";
import useSWR from "swr";
import { Table, TableBody, TableCell, TableRow } from "../Table";
+import { ContainerLoadingIndicator } from "../LoadingIndicator";
import useFormattedActivityData from "./useFormattedActivityData";
@@ -12,10 +13,10 @@ export default function ActivityTable({ type }) {
if (!items.length) {
return (
- {/* TODO: proper loading indicator / error message */}
- {!data && !error &&
Loading...
}
+ {/* TODO: proper error message */}
+ {!data && !error &&
}
{!data && error &&
An error occurred while loading this data.
}
- {data &&
No files found.
}
+ {data && !error &&
No files found.
}
);
}
diff --git a/packages/dashboard-v2/src/components/LatestActivity/LatestActivity.js b/packages/dashboard-v2/src/components/LatestActivity/LatestActivity.js
index 9c53554a..87825661 100644
--- a/packages/dashboard-v2/src/components/LatestActivity/LatestActivity.js
+++ b/packages/dashboard-v2/src/components/LatestActivity/LatestActivity.js
@@ -23,7 +23,7 @@ export default function LatestActivity() {
-
+
diff --git a/packages/dashboard-v2/src/components/LoadingIndicator/ContainerLoadingIndicator.js b/packages/dashboard-v2/src/components/LoadingIndicator/ContainerLoadingIndicator.js
new file mode 100644
index 00000000..de86a849
--- /dev/null
+++ b/packages/dashboard-v2/src/components/LoadingIndicator/ContainerLoadingIndicator.js
@@ -0,0 +1,18 @@
+import styled from "styled-components";
+import { CircledProgressIcon } from "../Icons";
+
+/**
+ * This loading indicator is designed to be replace entire blocks (i.e. components)
+ * while they are fetching required data.
+ *
+ * It will take 50% of the parent's height, but won't get bigger than 150x150 px.
+ */
+const Wrapper = styled.div.attrs({
+ className: "flex w-full h-full justify-center items-center p-8 text-palette-100",
+})``;
+
+export const ContainerLoadingIndicator = (props) => (
+
+
+
+);
diff --git a/packages/dashboard-v2/src/components/LoadingIndicator/index.js b/packages/dashboard-v2/src/components/LoadingIndicator/index.js
new file mode 100644
index 00000000..df7c2a88
--- /dev/null
+++ b/packages/dashboard-v2/src/components/LoadingIndicator/index.js
@@ -0,0 +1 @@
+export * from "./ContainerLoadingIndicator";
diff --git a/packages/dashboard-v2/src/components/NavBar/NavBar.js b/packages/dashboard-v2/src/components/NavBar/NavBar.js
index 2b53f2be..1db72dda 100644
--- a/packages/dashboard-v2/src/components/NavBar/NavBar.js
+++ b/packages/dashboard-v2/src/components/NavBar/NavBar.js
@@ -9,7 +9,7 @@ import { PageContainer } from "../PageContainer";
import { NavBarLink, NavBarSection } from ".";
const NavBarContainer = styled.div.attrs({
- className: `grid sticky top-0 bg-white`,
+ className: `grid sticky top-0 bg-white z-10 shadow-sm`,
})``;
const NavBarBody = styled.nav.attrs({
@@ -68,8 +68,21 @@ export const NavBar = () => (
-
-
+
+
diff --git a/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js b/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js
index e69de29b..1826cd92 100644
--- a/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js
+++ b/packages/dashboard-v2/src/components/PopoverMenu/PopoverMenu.js
@@ -0,0 +1,90 @@
+import { Children, cloneElement, useRef, useState } from "react";
+import PropTypes from "prop-types";
+import { useClickAway } from "react-use";
+import styled, { css, keyframes } from "styled-components";
+
+const dropDown = keyframes`
+ 0% {
+ transform: scaleY(0);
+ }
+ 80% {
+ transform: scaleY(1.1);
+ }
+ 100% {
+ transform: scaleY(1);
+ }
+`;
+
+const Container = styled.div.attrs({ className: "relative inline-flex" })``;
+
+const Flyout = styled.ul.attrs({
+ className: `absolute right-0 z-10 py-2
+ border rounded bg-white
+ overflow-hidden pointer-events-none
+ shadow-md shadow-palette-200/50
+ pointer-events-auto h-auto overflow-visible border-primary`,
+})`
+ top: calc(100% + 2px);
+ animation: ${css`
+ ${dropDown} 0.1s ease-in-out
+ `};
+`;
+
+const Option = styled.li.attrs({
+ className: `font-sans text-xs uppercase
+ relative pl-3 pr-5 py-1
+ text-palette-600 cursor-pointer
+ hover:text-primary hover:font-normal
+ active:text-primary active:font-normal
+
+ before:content-[initial] before:absolute before:left-0 before:h-3 before:w-0.5 before:bg-primary before:top-1.5
+ hover:before:content-['']`,
+})``;
+
+export const PopoverMenu = ({ options, children, openClassName, ...props }) => {
+ const containerRef = useRef();
+ const [open, setOpen] = useState(false);
+
+ useClickAway(containerRef, () => setOpen(false));
+
+ const handleChoice = (callback) => () => {
+ setOpen(false);
+ callback();
+ };
+
+ return (
+
+ {Children.only(
+ cloneElement(children, {
+ onClick: () => setOpen((open) => !open),
+ className: `${children.props.className ?? ""} ${open ? openClassName : ""}`,
+ })
+ )}
+ {open && (
+
+ {options.map(({ label, callback }) => (
+
+ {label}
+
+ ))}
+
+ )}
+
+ );
+};
+
+PopoverMenu.propTypes = {
+ /**
+ * Accepts a single child node that will become a menu toggle.
+ */
+ children: PropTypes.element.isRequired,
+ /**
+ * Positions in the menu
+ */
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ callback: PropTypes.func.isRequired,
+ })
+ ).isRequired,
+};
diff --git a/packages/dashboard-v2/src/components/Select/Select.js b/packages/dashboard-v2/src/components/Select/Select.js
index 97d3d73b..0a59a581 100644
--- a/packages/dashboard-v2/src/components/Select/Select.js
+++ b/packages/dashboard-v2/src/components/Select/Select.js
@@ -21,9 +21,11 @@ const dropDown = keyframes`
const Container = styled.div.attrs({ className: "relative inline-flex" })``;
-const Trigger = styled.button.attrs(({ placeholder }) => ({
- className: `flex items-center cursor-pointer ${placeholder ? "text-palette-300" : ""}`,
-}))``;
+const Trigger = styled.button.attrs(({ $placeholder }) => ({
+ className: `flex items-center cursor-pointer font-bold ${$placeholder ? "text-palette-300" : ""}`,
+}))`
+ text-transform: inherit;
+`;
const TriggerIcon = styled(ChevronDownIcon).attrs({
className: "transition-transform text-primary",
@@ -32,13 +34,14 @@ const TriggerIcon = styled(ChevronDownIcon).attrs({
`;
const Flyout = styled.ul.attrs(({ open }) => ({
- className: `absolute top-[20px] right-0
- p-0 h-0 border rounded bg-white
+ className: `absolute right-0 z-10
+ p-0 border rounded bg-white
overflow-hidden pointer-events-none
shadow-md shadow-palette-200/50
${open ? "pointer-events-auto h-auto overflow-visible border-primary" : ""}
${open ? "visible" : "invisible"}`,
}))`
+ top: calc(100% + 2px);
animation: ${({ open }) =>
open
? css`
@@ -47,7 +50,7 @@ const Flyout = styled.ul.attrs(({ open }) => ({
: "none"};
`;
-export const Select = ({ defaultValue, children, onChange, placeholder }) => {
+export const Select = ({ defaultValue, children, onChange, placeholder, ...props }) => {
const selectRef = useRef();
const options = useMemo(() => Children.toArray(children).filter(({ type }) => type === SelectOption), [children]);
const [state, dispatch] = useSelectReducer({ defaultValue, placeholder, options });
@@ -65,8 +68,8 @@ export const Select = ({ defaultValue, children, onChange, placeholder }) => {
const activeLabel = activeOption?.props?.label ?? null;
return (
-
-
+
+
{activeLabel ?? placeholder}
diff --git a/packages/dashboard-v2/src/components/Switch/Switch.css b/packages/dashboard-v2/src/components/Switch/Switch.css
deleted file mode 100644
index 4bd07cf2..00000000
--- a/packages/dashboard-v2/src/components/Switch/Switch.css
+++ /dev/null
@@ -1,40 +0,0 @@
-.react-switch-checkbox {
- height: 0;
- width: 0;
- visibility: hidden;
-}
-
-.react-switch-label {
- display: flex;
- align-items: center;
- justify-content: space-between;
- cursor: pointer;
- width: 44px;
- height: 22px;
- background: white;
- border-radius: 11px;
- @apply border-palette-200;
- border-width: 1px;
- position: relative;
- transition: background-color 0.2s;
-}
-
-.react-switch-label .react-switch-button {
- content: "";
- position: absolute;
- top: 2px;
- left: 2px;
- width: 16px;
- height: 16px;
- border-radius: 8px;
- transition: 0.2s;
-}
-
-.react-switch-checkbox:checked + .react-switch-label .react-switch-button {
- left: calc(100% - 2px);
- transform: translateX(-100%);
-}
-
-.react-switch-label:active .react-switch-button {
- width: 20px;
-}
diff --git a/packages/dashboard-v2/src/components/Switch/Switch.js b/packages/dashboard-v2/src/components/Switch/Switch.js
index 7709412b..892692be 100644
--- a/packages/dashboard-v2/src/components/Switch/Switch.js
+++ b/packages/dashboard-v2/src/components/Switch/Switch.js
@@ -1,37 +1,91 @@
import PropTypes from "prop-types";
-import "./Switch.css";
+import { useEffect, useMemo, useState } from "react";
+import styled from "styled-components";
+import { nanoid } from "nanoid";
+
+const Container = styled.div.attrs({
+ className: "inline-flex items-center gap-1 cursor-pointer select-none",
+})``;
+
+const Checkbox = styled.input.attrs({
+ type: "checkbox",
+ className: `h-0 w-0 hidden`,
+})``;
+
+const Label = styled.label.attrs({
+ className: "cursor-pointer inline-flex items-center gap-2",
+})`
+ &:active .toggle-pin {
+ width: 20px;
+ }
+`;
+
+const Toggle = styled.span.attrs({
+ className: `flex flex-row items-center justify-between shrink-0
+ w-[44px] h-[22px] bg-white rounded-full
+ border border-palette-200 relative cursor-pointer`,
+})`
+ &:active .toggle-pin {
+ width: 20px;
+ }
+`;
+
+const TogglePin = styled.span.attrs(({ $checked }) => ({
+ className: `toggle-pin
+ absolute top-[2px] w-4 h-4 rounded-full
+ transition-[width_left] active:w-5
+
+ ${$checked ? "checked bg-primary" : "bg-palette-200"}`,
+}))`
+ left: 2px;
+
+ &.checked {
+ left: calc(100% - 2px);
+ transform: translateX(-100%);
+ }
+`;
+
+export const Switch = ({ children, defaultChecked, labelClassName, onChange, ...props }) => {
+ const id = useMemo(nanoid, [onChange]);
+ const [checked, setChecked] = useState(defaultChecked);
+
+ useEffect(() => {
+ onChange(checked);
+ }, [checked, onChange]);
-/**
- * Primary UI component for user interaction
- */
-export const Switch = ({ isOn, handleToggle }) => {
return (
- <>
-
-
-
-
- >
+
+ setChecked(ev.target.checked)} id={id} />
+
+
+
+
+ {children}
+
+
);
};
Switch.propTypes = {
/**
- * Switch's current value
+ * Should the checkbox be checked by default?
*/
- isOn: PropTypes.bool,
+ defaultChecked: PropTypes.bool,
+ /**
+ * Element to be rendered as the switch label
+ */
+ children: PropTypes.oneOfType([PropTypes.element, PropTypes.string]),
+ /**
+ * Pass additional CSS classes to the `label` element.
+ */
+ labelClassName: PropTypes.string,
/**
* Function to execute on change
*/
- handleToggle: PropTypes.func,
+ onChange: PropTypes.func.isRequired,
};
Switch.defaultProps = {
- isOn: false,
+ defaultChecked: false,
+ labelClassName: "",
};
diff --git a/packages/dashboard-v2/src/components/Switch/Switch.stories.js b/packages/dashboard-v2/src/components/Switch/Switch.stories.js
index 41d64f6b..8e0cba7d 100644
--- a/packages/dashboard-v2/src/components/Switch/Switch.stories.js
+++ b/packages/dashboard-v2/src/components/Switch/Switch.stories.js
@@ -13,10 +13,10 @@ const Template = (args) => ;
export const SwitchTrue = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
SwitchTrue.args = {
- isOn: true,
+ defaultChecked: true,
};
export const SwitchFalse = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
SwitchFalse.args = {
- isOn: false,
+ defaultChecked: false,
};
diff --git a/packages/dashboard-v2/src/components/Table/Table.js b/packages/dashboard-v2/src/components/Table/Table.js
index 8d55e119..e741da20 100644
--- a/packages/dashboard-v2/src/components/Table/Table.js
+++ b/packages/dashboard-v2/src/components/Table/Table.js
@@ -1,7 +1,7 @@
import styled from "styled-components";
const Container = styled.div.attrs({
- className: "p-1 max-w-full overflow-x-auto",
+ className: "p-1 max-w-full",
})``;
const StyledTable = styled.table.attrs({
diff --git a/packages/dashboard-v2/src/components/Table/TableHeadCell.js b/packages/dashboard-v2/src/components/Table/TableHeadCell.js
index aeb65670..f16530f0 100644
--- a/packages/dashboard-v2/src/components/Table/TableHeadCell.js
+++ b/packages/dashboard-v2/src/components/Table/TableHeadCell.js
@@ -4,7 +4,8 @@ import styled from "styled-components";
* Accepts all HMTL attributes a `` element does.
*/
export const TableHeadCell = styled.th.attrs({
- className: `px-6 py-2.5 truncate h-tableRow
+ className: `first:pl-6 last:pr-6 px-2 py-4
+ truncate h-tableRow
text-palette-600 font-sans font-light text-xs
first:rounded-l-sm last:rounded-r-sm`,
})`
diff --git a/packages/dashboard-v2/src/components/Tabs/Tabs.js b/packages/dashboard-v2/src/components/Tabs/Tabs.js
index 4bf20ccf..eae51a65 100644
--- a/packages/dashboard-v2/src/components/Tabs/Tabs.js
+++ b/packages/dashboard-v2/src/components/Tabs/Tabs.js
@@ -34,7 +34,11 @@ const Body = styled.div.attrs({ className: "grow min-h-0" })``;
export const Tabs = ({ defaultTab, children, variant }) => {
const getTabId = usePrefixedTabIds();
const { tabs, panels, tabsRefs } = useTabsChildren(children, getTabId);
- const defaultTabId = useMemo(() => getTabId(defaultTab || tabs[0].props.id), [getTabId, defaultTab, tabs]);
+ const defaultTabId = useMemo(() => {
+ const requestedTabIsPresent = tabs.find(({ props }) => props.id === defaultTab);
+
+ return getTabId(requestedTabIsPresent ? defaultTab : tabs[0].props.id);
+ }, [getTabId, defaultTab, tabs]);
const [activeTabId, setActiveTabId] = useState(defaultTabId);
const [activeTabRef, setActiveTabRef] = useState(tabsRefs[activeTabId]);
const isActive = (id) => id === activeTabId;
diff --git a/packages/dashboard-v2/src/components/TextInputBasic/TextInputBasic.js b/packages/dashboard-v2/src/components/TextInputBasic/TextInputBasic.js
index 9fa7f670..6b9531b4 100644
--- a/packages/dashboard-v2/src/components/TextInputBasic/TextInputBasic.js
+++ b/packages/dashboard-v2/src/components/TextInputBasic/TextInputBasic.js
@@ -1,18 +1,20 @@
+import { nanoid } from "nanoid";
import PropTypes from "prop-types";
+import { useMemo } from "react";
+
+export const TextInputBasic = ({ label, placeholder, ...props }) => {
+ const id = useMemo(() => `input-${nanoid()}`, []);
-/**
- * Primary UI component for user interaction
- */
-export const TextInputBasic = ({ label, placeholder }) => {
return (
-
-
{label}
+
+
+ {label}
+
);
diff --git a/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.js b/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.js
index 892da996..506ee1fc 100644
--- a/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.js
+++ b/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.js
@@ -1,20 +1,45 @@
import PropTypes from "prop-types";
+import cn from "classnames";
+import { useEffect, useRef, useState } from "react";
+import { PlusIcon } from "../Icons";
+
+export const TextInputIcon = ({ className, icon, placeholder, onChange }) => {
+ const inputRef = useRef();
+ const [focused, setFocused] = useState(false);
+ const [value, setValue] = useState("");
+
+ useEffect(() => {
+ onChange(value);
+ }, [value, onChange]);
-/**
- * Primary UI component for user interaction
- */
-export const TextInputIcon = ({ icon, position, placeholder }) => {
return (
-
- {position === "left" ?
{icon}
: null}
-
+
{icon}
+
setFocused(true)}
+ onBlur={() => setFocused(false)}
+ onChange={(event) => setValue(event.target.value)}
+ placeholder={placeholder}
+ className="focus:outline-none bg-transparent placeholder:text-palette-400"
/>
- {position === "right" ?
{icon}
: null}
+ {value && (
+
setValue("")}
+ />
+ )}
);
};
@@ -23,13 +48,13 @@ TextInputIcon.propTypes = {
/**
* Icon to place in text input
*/
- icon: PropTypes.element,
- /**
- * Side to place icon
- */
- position: PropTypes.oneOf(["left", "right"]),
+ icon: PropTypes.element.isRequired,
/**
* Input placeholder
*/
placeholder: PropTypes.string,
+ /**
+ * Function to be called whenever value changes
+ */
+ onChange: PropTypes.func.isRequired,
};
diff --git a/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.stories.js b/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.stories.js
index 676ca9cf..521b90df 100644
--- a/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.stories.js
+++ b/packages/dashboard-v2/src/components/TextInputIcon/TextInputIcon.stories.js
@@ -1,5 +1,6 @@
import { TextInputIcon } from "./TextInputIcon";
-import { CogIcon } from "../Icons";
+import { SearchIcon } from "../Icons";
+import { Panel } from "../Panel";
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
@@ -9,19 +10,21 @@ export default {
};
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
-const Template = (args) =>
;
+const Template = (args) => (
+
+
+
+);
export const IconLeft = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
IconLeft.args = {
- icon:
,
- position: "left",
+ icon:
,
placeholder: "Search",
};
export const IconRight = Template.bind({});
IconRight.args = {
- icon:
,
- position: "right",
+ icon:
,
placeholder: "Search",
};
diff --git a/packages/dashboard-v2/src/contexts/plans/PlansContext.js b/packages/dashboard-v2/src/contexts/plans/PlansContext.js
new file mode 100644
index 00000000..ff35b45e
--- /dev/null
+++ b/packages/dashboard-v2/src/contexts/plans/PlansContext.js
@@ -0,0 +1,7 @@
+import { createContext } from "react";
+
+export const PlansContext = createContext({
+ plans: [],
+ limits: [],
+ error: null,
+});
diff --git a/packages/dashboard-v2/src/contexts/plans/PlansProvider.js b/packages/dashboard-v2/src/contexts/plans/PlansProvider.js
new file mode 100644
index 00000000..c481e296
--- /dev/null
+++ b/packages/dashboard-v2/src/contexts/plans/PlansProvider.js
@@ -0,0 +1,47 @@
+import { useEffect, useState } from "react";
+import useSWR from "swr";
+
+import freePlan from "../../lib/tiers";
+
+import { PlansContext } from "./PlansContext";
+
+/**
+ * NOTE: this function heavily relies on the fact that each Plan's `tier`
+ * property corresponds to the plan's index in UserLimits array in
+ * skynet-accounts code.
+ *
+ * @see https://github.com/SkynetLabs/skynet-accounts/blob/7337e740b71b77e6d08016db801e293b8ad81abc/database/user.go#L53-L101
+ */
+const aggregatePlansAndLimits = (plans, limits) => {
+ const sortedPlans = [freePlan, ...plans].sort((planA, planB) => planA.tier - planB.tier);
+
+ // Decorate each plan with its corresponding limits data, if available.
+ if (limits?.length) {
+ return sortedPlans.map((plan) => ({ ...plan, limits: limits[plan.tier] || null }));
+ }
+
+ // If we don't have the limits data yet, set just return the plans.
+
+ return sortedPlans;
+};
+
+export const PlansProvider = ({ children }) => {
+ const { data: rawPlans, error: plansError } = useSWR("stripe/prices");
+ const { data: limits, error: limitsError } = useSWR("limits");
+
+ const [plans, setPlans] = useState([freePlan]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (plansError || limitsError) {
+ setLoading(false);
+ setError(plansError || limitsError);
+ } else if (rawPlans) {
+ setLoading(false);
+ setPlans(aggregatePlansAndLimits(rawPlans, limits?.userLimits));
+ }
+ }, [rawPlans, limits, plansError, limitsError]);
+
+ return
{children} ;
+};
diff --git a/packages/dashboard-v2/src/contexts/plans/index.js b/packages/dashboard-v2/src/contexts/plans/index.js
new file mode 100644
index 00000000..84dd790f
--- /dev/null
+++ b/packages/dashboard-v2/src/contexts/plans/index.js
@@ -0,0 +1,2 @@
+export * from "./PlansProvider";
+export * from "./usePlans";
diff --git a/packages/dashboard-v2/src/contexts/plans/usePlans.js b/packages/dashboard-v2/src/contexts/plans/usePlans.js
new file mode 100644
index 00000000..f36e8595
--- /dev/null
+++ b/packages/dashboard-v2/src/contexts/plans/usePlans.js
@@ -0,0 +1,5 @@
+import { useContext } from "react";
+
+import { PlansContext } from "./PlansContext";
+
+export const usePlans = () => useContext(PlansContext);
diff --git a/packages/dashboard-v2/src/hooks/useActivePlan.js b/packages/dashboard-v2/src/hooks/useActivePlan.js
new file mode 100644
index 00000000..53e28b63
--- /dev/null
+++ b/packages/dashboard-v2/src/hooks/useActivePlan.js
@@ -0,0 +1,22 @@
+import { useEffect, useState } from "react";
+
+import freeTier from "../lib/tiers";
+import { usePlans } from "../contexts/plans";
+
+export default function useActivePlan(user) {
+ const { plans, error } = usePlans();
+
+ const [activePlan, setActivePlan] = useState(freeTier);
+
+ useEffect(() => {
+ if (user) {
+ setActivePlan(plans.find((plan) => plan.tier === user.tier));
+ }
+ }, [plans, user]);
+
+ return {
+ error,
+ plans,
+ activePlan,
+ };
+}
diff --git a/packages/dashboard-v2/src/hooks/useSubscriptionPlans.js b/packages/dashboard-v2/src/hooks/useSubscriptionPlans.js
deleted file mode 100644
index 26658df8..00000000
--- a/packages/dashboard-v2/src/hooks/useSubscriptionPlans.js
+++ /dev/null
@@ -1,28 +0,0 @@
-import { useEffect, useState } from "react";
-import useSWR from "swr";
-import freeTier from "../lib/tiers";
-
-export default function useSubscriptionPlans(user) {
- const { data: paidPlans, error, mutate } = useSWR("stripe/prices");
- const [plans, setPlans] = useState([freeTier]);
- const [activePlan, setActivePlan] = useState(freeTier);
-
- useEffect(() => {
- if (paidPlans) {
- setPlans((plans) => [...plans, ...paidPlans].sort((planA, planB) => planA.tier - planB.tier));
- }
- }, [paidPlans]);
-
- useEffect(() => {
- if (user) {
- setActivePlan(plans.find((plan) => plan.tier === user.tier));
- }
- }, [plans, user]);
-
- return {
- error,
- mutate,
- plans,
- activePlan,
- };
-}
diff --git a/packages/dashboard-v2/src/layouts/DashboardLayout.js b/packages/dashboard-v2/src/layouts/DashboardLayout.js
index 07f4eabf..b369ece3 100644
--- a/packages/dashboard-v2/src/layouts/DashboardLayout.js
+++ b/packages/dashboard-v2/src/layouts/DashboardLayout.js
@@ -8,6 +8,7 @@ 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",
@@ -25,7 +26,7 @@ const Layout = ({ children }) => {
{!user && (
-
Loading...
{/* TODO: Do something nicer here */}
+
)}
{user && <>{children}>}
diff --git a/packages/dashboard-v2/src/layouts/UserSettingsLayout.js b/packages/dashboard-v2/src/layouts/UserSettingsLayout.js
new file mode 100644
index 00000000..9fda613d
--- /dev/null
+++ b/packages/dashboard-v2/src/layouts/UserSettingsLayout.js
@@ -0,0 +1,87 @@
+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 (
+
+ {!user && (
+
+
+
+ )}
+ {user && <>{children}>}
+
+ );
+};
+
+const Sidebar = () => (
+
+
+
+ Account
+
+
+ Notifications
+
+
+ Import / Export
+
+
+ API Keys
+
+
+
+);
+
+const SidebarLink = styled(Link).attrs({
+ className: `h-12 py-3 px-6 h-full w-full flex
+ border-l-2 border-l-palette-200
+ border-b border-b-palette-100 last:border-b-transparent`,
+})``;
+
+const Content = styled.main.attrs({
+ className: "relative bg-white rounded px-6 py-6 sm:px-16 sm:py-14 mt-6 lg:mt-0 bg-none xl:bg-corner-circle",
+})`
+ background-repeat: no-repeat;
+`;
+
+const UserSettingsLayout = ({ children }) => (
+
+
+
+
+
+ Settings
+
+
+ {children}
+
+
+
+
+
+
+);
+
+export default UserSettingsLayout;
diff --git a/packages/dashboard-v2/src/pages/files.js b/packages/dashboard-v2/src/pages/files.js
index 348a95b3..197cc031 100644
--- a/packages/dashboard-v2/src/pages/files.js
+++ b/packages/dashboard-v2/src/pages/files.js
@@ -2,8 +2,28 @@ import * as React from "react";
import DashboardLayout from "../layouts/DashboardLayout";
+import { Panel } from "../components/Panel";
+import { Tab, TabPanel, Tabs } from "../components/Tabs";
+import FileList from "../components/FileList/FileList";
+import { useSearchParam } from "react-use";
+
const FilesPage = () => {
- return <>FILES>;
+ const defaultTab = useSearchParam("tab");
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ );
};
FilesPage.Layout = DashboardLayout;
diff --git a/packages/dashboard-v2/src/pages/index.js b/packages/dashboard-v2/src/pages/index.js
index 695e6ac3..4db97e04 100644
--- a/packages/dashboard-v2/src/pages/index.js
+++ b/packages/dashboard-v2/src/pages/index.js
@@ -2,6 +2,7 @@ import * as React from "react";
import { useMedia } from "react-use";
import theme from "../lib/theme";
+import { PlansProvider } from "../contexts/plans/PlansProvider";
import { ArrowRightIcon } from "../components/Icons";
import { Panel } from "../components/Panel";
import { Tab, TabPanel, Tabs } from "../components/Tabs";
@@ -16,7 +17,7 @@ const IndexPage = () => {
const showRecentActivity = useMedia(`(min-width: ${theme.screens.md})`);
return (
- <>
+
{
)}
- >
+
);
};
diff --git a/packages/dashboard-v2/src/pages/settings/api-keys.js b/packages/dashboard-v2/src/pages/settings/api-keys.js
new file mode 100644
index 00000000..56d6977e
--- /dev/null
+++ b/packages/dashboard-v2/src/pages/settings/api-keys.js
@@ -0,0 +1,15 @@
+import * as React from "react";
+
+import UserSettingsLayout from "../../layouts/UserSettingsLayout";
+
+const APIKeysPage = () => {
+ return (
+ <>
+ API Keys
+ >
+ );
+};
+
+APIKeysPage.Layout = UserSettingsLayout;
+
+export default APIKeysPage;
diff --git a/packages/dashboard-v2/src/pages/settings/export.js b/packages/dashboard-v2/src/pages/settings/export.js
new file mode 100644
index 00000000..feac6719
--- /dev/null
+++ b/packages/dashboard-v2/src/pages/settings/export.js
@@ -0,0 +1,15 @@
+import * as React from "react";
+
+import UserSettingsLayout from "../../layouts/UserSettingsLayout";
+
+const ExportPage = () => {
+ return (
+ <>
+ Import / export
+ >
+ );
+};
+
+ExportPage.Layout = UserSettingsLayout;
+
+export default ExportPage;
diff --git a/packages/dashboard-v2/src/pages/settings/index.js b/packages/dashboard-v2/src/pages/settings/index.js
new file mode 100644
index 00000000..459cf8c0
--- /dev/null
+++ b/packages/dashboard-v2/src/pages/settings/index.js
@@ -0,0 +1,81 @@
+import * as React from "react";
+import { useMedia } from "react-use";
+import styled from "styled-components";
+
+import theme from "../../lib/theme";
+import UserSettingsLayout from "../../layouts/UserSettingsLayout";
+import { TextInputBasic } from "../../components/TextInputBasic/TextInputBasic";
+import { Button } from "../../components/Button";
+import { AvatarUploader } from "../../components/AvatarUploader";
+
+const FormGroup = styled.div.attrs({
+ className: "grid sm:grid-cols-[1fr_min-content] w-full gap-y-2 gap-x-4 items-end",
+})``;
+
+const AccountPage = () => {
+ const isLargeScreen = useMedia(`(min-width: ${theme.screens.xl})`);
+ return (
+ <>
+
+
+
+ Account
+
+ Tum dicere exorsus est laborum et quasi involuta aperiri, altera prompta et expedita. Primum igitur,
+ inquit, modo ista sis aequitate.
+
+
+
+ {!isLargeScreen && (
+
+ )}
+
+
+
+
+ Update
+
+
+
+
+
+ Update
+
+
+
+
+
+ Update
+
+
+ The password must be at least 6 characters long. Significantly different from the email and old
+ password.
+
+
+
+
+
+ Delete account
+ This will completely delete your account. This process can't be undone.
+ window.confirm("TODO: confirmation modal")}
+ className="text-error underline decoration-1 hover:decoration-dashed"
+ >
+ Delete account
+
+
+
+
+
+ >
+ );
+};
+
+AccountPage.Layout = UserSettingsLayout;
+
+export default AccountPage;
diff --git a/packages/dashboard-v2/src/pages/settings/notifications.js b/packages/dashboard-v2/src/pages/settings/notifications.js
new file mode 100644
index 00000000..b46a1da4
--- /dev/null
+++ b/packages/dashboard-v2/src/pages/settings/notifications.js
@@ -0,0 +1,51 @@
+import * as React from "react";
+
+import UserSettingsLayout from "../../layouts/UserSettingsLayout";
+
+import { Switch } from "../../components/Switch";
+import { StaticImage } from "gatsby-plugin-image";
+
+const NotificationsPage = () => {
+ return (
+ <>
+
+
+
Notifications
+
+ {/* TODO: saves on change */}
+
+ I agreee to get the latest news, updates and special offers delivered to my email inbox.
+
+
+
+
+ Statistics
+ {/* TODO: proper content :) */}
+
+ Si sine causa, nollem me tamen laudandis maioribus meis corrupisti nec in malis. Si sine causa, mox
+ videro.
+
+
+
+
+ {/* TODO: saves on change */}
+ Storage limit
+
+
+ {/* TODO: saves on change */}
+ File limit
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+NotificationsPage.Layout = UserSettingsLayout;
+
+export default NotificationsPage;
diff --git a/packages/dashboard-v2/src/styles/global.css b/packages/dashboard-v2/src/styles/global.css
index ae86596a..e0238aad 100644
--- a/packages/dashboard-v2/src/styles/global.css
+++ b/packages/dashboard-v2/src/styles/global.css
@@ -39,7 +39,14 @@
font-size: 1rem;
}
h6 {
- @apply uppercase;
- font-size: 0.875rem;
+ @apply uppercase text-xs;
+ }
+
+ p {
+ @apply mt-2;
+ }
+
+ hr {
+ @apply border-t-palette-200;
}
}
diff --git a/packages/dashboard-v2/static/images/avatar-bg.svg b/packages/dashboard-v2/static/images/avatar-bg.svg
new file mode 100644
index 00000000..4095e0a0
--- /dev/null
+++ b/packages/dashboard-v2/static/images/avatar-bg.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/dashboard-v2/static/images/avatar-placeholder.svg b/packages/dashboard-v2/static/images/avatar-placeholder.svg
new file mode 100644
index 00000000..8fae86b7
--- /dev/null
+++ b/packages/dashboard-v2/static/images/avatar-placeholder.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/dashboard-v2/static/images/inbox.svg b/packages/dashboard-v2/static/images/inbox.svg
new file mode 100644
index 00000000..389be116
--- /dev/null
+++ b/packages/dashboard-v2/static/images/inbox.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/dashboard-v2/tailwind.config.js b/packages/dashboard-v2/tailwind.config.js
index 4afa0cb2..076d6912 100644
--- a/packages/dashboard-v2/tailwind.config.js
+++ b/packages/dashboard-v2/tailwind.config.js
@@ -28,6 +28,7 @@ module.exports = {
borderColor: (theme) => ({ ...theme("colors"), ...colors }),
textColor: (theme) => ({ ...theme("colors"), ...colors }),
placeholderColor: (theme) => ({ ...theme("colors"), ...colors }),
+ outlineColor: (theme) => ({ ...theme("colors"), ...colors }),
extend: {
fontFamily: {
sans: ["Sora", ...defaultTheme.fontFamily.sans],
@@ -38,6 +39,10 @@ module.exports = {
tab: ["18px", "28px"],
},
backgroundColor: ["disabled"],
+ backgroundImage: {
+ "corner-circle":
+ "radial-gradient(circle at calc(100% - 60px) -50px, #F5F5F7 0%, #f5f5f7 250px,rgba(0,0,0,0) 250px)",
+ },
textColor: ["disabled"],
keyframes: {
wiggle: {
@@ -53,6 +58,8 @@ module.exports = {
"page-md": "640px",
"page-lg": "896px",
"page-xl": "1312px",
+ "settings-lg": "704px",
+ "settings-xl": "928px",
},
minWidth: {
button: "112px",
diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json
index 23f9f77e..869b77dd 100644
--- a/packages/dashboard/package.json
+++ b/packages/dashboard/package.json
@@ -9,7 +9,7 @@
},
"dependencies": {
"@fontsource/sora": "4.5.3",
- "@fontsource/source-sans-pro": "4.5.3",
+ "@fontsource/source-sans-pro": "4.5.4",
"@stripe/react-stripe-js": "1.7.0",
"@stripe/stripe-js": "1.24.0",
"classnames": "2.3.1",
@@ -27,7 +27,7 @@
"react-dom": "17.0.2",
"react-toastify": "8.2.0",
"skynet-js": "3.0.2",
- "stripe": "8.207.0",
+ "stripe": "8.209.0",
"swr": "1.2.2",
"yup": "0.32.11"
},
@@ -35,7 +35,7 @@
"@tailwindcss/forms": "0.5.0",
"@tailwindcss/typography": "0.5.2",
"autoprefixer": "10.4.2",
- "eslint": "8.10.0",
+ "eslint": "8.11.0",
"eslint-config-next": "12.1.0",
"postcss": "8.4.8",
"prettier": "2.5.1",
diff --git a/packages/dashboard/yarn.lock b/packages/dashboard/yarn.lock
index 231968e3..50328d5a 100644
--- a/packages/dashboard/yarn.lock
+++ b/packages/dashboard/yarn.lock
@@ -38,16 +38,16 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@eslint/eslintrc@^1.2.0":
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.0.tgz#7ce1547a5c46dfe56e1e45c3c9ed18038c721c6a"
- integrity sha512-igm9SjJHNEJRiUnecP/1R5T3wKLEJ7pL6e2P+GUSfCd0dGjPYYZve08uzw8L2J8foVHFz+NGu12JxRcU2gGo6w==
+"@eslint/eslintrc@^1.2.1":
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6"
+ integrity sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.3.1"
globals "^13.9.0"
- ignore "^4.0.6"
+ ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.0.4"
@@ -58,10 +58,10 @@
resolved "https://registry.yarnpkg.com/@fontsource/sora/-/sora-4.5.3.tgz#987c9b43acb00c9e3fa5377ebcedfd4ec9b760a7"
integrity sha512-0ipYkroLonvChAyLajgIt6mImXMhvjrHwD5g7iX2ZR1eJ4hLDnwq6haW5pSeehe79lPjgp0BX6ZHivFIP0xR2g==
-"@fontsource/source-sans-pro@4.5.3":
- version "4.5.3"
- resolved "https://registry.yarnpkg.com/@fontsource/source-sans-pro/-/source-sans-pro-4.5.3.tgz#bdb1eeed5db70bcd1f68cd1e8c859834f0e6bc67"
- integrity sha512-9xWGu3ArKsjf6+WVrNoCUywybTB3rIidpvOI2tByQpzYVOupFUv6qohyrjDrVvPb6XHJQTD0NIzisR7RKhiP7A==
+"@fontsource/source-sans-pro@4.5.4":
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/@fontsource/source-sans-pro/-/source-sans-pro-4.5.4.tgz#51510723ff40f446c7800f133e9ae604ae2f38d7"
+ integrity sha512-+YYw6HRvH9wYE+U2Hvxyossg+MHPApAj7VIjEqaXenNeNQa4U3uPD0e7pc+1Gic3srCQATN15O3S9WSFLXTmwQ==
"@humanwhocodes/config-array@^0.9.2":
version "0.9.2"
@@ -911,12 +911,12 @@ 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.10.0:
- version "8.10.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.10.0.tgz#931be395eb60f900c01658b278e05b6dae47199d"
- integrity sha512-tcI1D9lfVec+R4LE1mNDnzoJ/f71Kl/9Cv4nG47jOueCMBrCCKYXr4AUVS7go6mWYGFD4+EoN6+eXSrEbRzXVw==
+eslint@8.11.0:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.11.0.tgz#88b91cfba1356fc10bb9eb592958457dfe09fb37"
+ integrity sha512-/KRpd9mIRg2raGxHRGwW9ZywYNAClZrHjdueHcrVDuO3a6bj83eoTirCCk0M0yPwOjWYKHwRVRid+xK4F/GHgA==
dependencies:
- "@eslint/eslintrc" "^1.2.0"
+ "@eslint/eslintrc" "^1.2.1"
"@humanwhocodes/config-array" "^0.9.2"
ajv "^6.10.0"
chalk "^4.0.0"
@@ -1247,11 +1247,6 @@ ieee754@^1.2.1:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
-ignore@^4.0.6:
- version "4.0.6"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
- integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-
ignore@^5.1.4, ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
@@ -2255,10 +2250,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.207.0:
- version "8.207.0"
- resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.207.0.tgz#4b7002f19cecefbc3c48f09f6658c39e359f99c1"
- integrity sha512-ZCjdqN2adGfrC5uAAo0v7IquzaiQ3+pDzB324/iV3Q3Deiot9VO7KMVSNVx/0i6E6ywhgV33ko3FMT7iUgxKYA==
+stripe@8.209.0:
+ version "8.209.0"
+ resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.209.0.tgz#a8f34132fb4140bdf9152943b15c641ad99cd3b1"
+ integrity sha512-ozfs8t0fxA/uvCK1DNvitSdEublOHK5CTRsrd2AWWk9LogjXcfkxmtz3KGSSQd+jyA2+rbee9TMzhJ6aabQ5WQ==
dependencies:
"@types/node" ">=8.1.0"
qs "^6.6.0"
diff --git a/packages/health-check/src/checks/critical.js b/packages/health-check/src/checks/critical.js
index 9eec00fb..127ebd8a 100644
--- a/packages/health-check/src/checks/critical.js
+++ b/packages/health-check/src/checks/critical.js
@@ -201,11 +201,7 @@ async function genericAccessCheck(name, url) {
const data = { up: false, url };
try {
- const cookie = `nocache=true;${authCookie}`;
- const response = await got(url, {
- headers: { cookie },
- hooks: { beforeRedirect: [(options) => (options.headers.cookie = cookie)] },
- });
+ const response = await got(url, { headers: { cookie: `nocache=true;${authCookie}` } });
data.statusCode = response.statusCode;
data.up = true;
diff --git a/packages/health-check/src/checks/extended.js b/packages/health-check/src/checks/extended.js
index bf8d4b99..aaf74389 100644
--- a/packages/health-check/src/checks/extended.js
+++ b/packages/health-check/src/checks/extended.js
@@ -1023,27 +1023,13 @@ function fileEndpointCheck(done) {
}
// check whether hns/note-to-self would properly redirect to note-to-self/
-function skylinkRootDomainEndpointRedirect(done) {
+function hnsEndpointDirectoryRedirect(done) {
const expected = {
- name: "skylink root domain endpoint redirect",
- skylink: "AACogzrAimYPG42tDOKhS3lXZD8YvlF8Q8R17afe95iV2Q",
- statusCode: 301,
- headers: {
- location: `https://000ah0pqo256c3orhmmgpol19dslep1v32v52v23ohqur9uuuuc9bm8.${process.env.PORTAL_DOMAIN}`,
- },
- };
-
- skylinkVerification(done, expected, { followRedirect: false });
-}
-
-// check whether hns/note-to-self would properly redirect to note-to-self/
-function hnsRootDomainEndpointRedirect(done) {
- const expected = {
- name: "hns root domain endpoint redirect",
+ name: "hns endpoint directory redirect",
skylink: "hns/note-to-self",
- statusCode: 301,
+ statusCode: 308,
headers: {
- location: `https://note-to-self.hns.${process.env.PORTAL_DOMAIN}`,
+ location: "note-to-self/",
},
};
@@ -1150,12 +1136,7 @@ async function skylinkVerification(done, expected, { followRedirect = true, meth
try {
const query = `https://${process.env.PORTAL_DOMAIN}/${expected.skylink}`;
- const cookie = `nocache=true;${authCookie}`;
- const response = await got[method](query, {
- followRedirect,
- headers: { cookie },
- hooks: { beforeRedirect: [(options) => (options.headers.cookie = cookie)] },
- });
+ const response = await got[method](query, { followRedirect, headers: { cookie: `nocache=true;${authCookie}` } });
const entry = { ...details, up: true, statusCode: response.statusCode, time: calculateElapsedTime(time) };
const info = {};
@@ -1256,8 +1237,7 @@ module.exports = [
// uniswapHNSRedirectCheck,
uniswapHNSResolverCheck,
uniswapHNSResolverRedirectCheck,
- skylinkRootDomainEndpointRedirect,
- hnsRootDomainEndpointRedirect,
+ hnsEndpointDirectoryRedirect,
skappSkySend,
skappNoteToSelf,
skappUniswap,
diff --git a/packages/website/package.json b/packages/website/package.json
index 38de206a..e4d888d7 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -6,24 +6,24 @@
"author": "Skynet Labs.",
"dependencies": {
"@fontsource/sora": "4.5.3",
- "@fontsource/source-sans-pro": "4.5.3",
+ "@fontsource/source-sans-pro": "4.5.4",
"@svgr/webpack": "6.2.1",
"bytes": "3.1.2",
"classnames": "2.3.1",
"copy-text-to-clipboard": "3.0.1",
"crypto-browserify": "3.12.0",
"framer-motion": "6.2.8",
- "gatsby": "4.9.2",
+ "gatsby": "4.9.3",
"gatsby-background-image": "1.6.0",
"gatsby-plugin-image": "2.9.0",
- "gatsby-plugin-manifest": "4.9.0",
+ "gatsby-plugin-manifest": "4.9.1",
"gatsby-plugin-postcss": "5.9.0",
"gatsby-plugin-react-helmet": "5.9.0",
"gatsby-plugin-robots-txt": "1.7.0",
- "gatsby-plugin-sharp": "4.9.0",
+ "gatsby-plugin-sharp": "4.9.1",
"gatsby-plugin-sitemap": "5.9.0",
"gatsby-plugin-svgr": "3.0.0-beta.0",
- "gatsby-source-filesystem": "4.9.0",
+ "gatsby-source-filesystem": "4.9.1",
"gatsby-transformer-sharp": "4.9.0",
"gatsby-transformer-yaml": "4.9.0",
"gbimage-bridge": "0.2.1",
@@ -40,7 +40,7 @@
"react-dropzone": "12.0.4",
"react-helmet": "6.1.0",
"react-use": "17.3.2",
- "skynet-js": "4.0.23-beta",
+ "skynet-js": "4.0.26-beta",
"stream-browserify": "3.0.0",
"swr": "1.2.2"
},
diff --git a/packages/website/yarn.lock b/packages/website/yarn.lock
index 7ef765a0..f671716a 100644
--- a/packages/website/yarn.lock
+++ b/packages/website/yarn.lock
@@ -1103,10 +1103,10 @@
resolved "https://registry.yarnpkg.com/@fontsource/sora/-/sora-4.5.3.tgz#987c9b43acb00c9e3fa5377ebcedfd4ec9b760a7"
integrity sha512-0ipYkroLonvChAyLajgIt6mImXMhvjrHwD5g7iX2ZR1eJ4hLDnwq6haW5pSeehe79lPjgp0BX6ZHivFIP0xR2g==
-"@fontsource/source-sans-pro@4.5.3":
- version "4.5.3"
- resolved "https://registry.yarnpkg.com/@fontsource/source-sans-pro/-/source-sans-pro-4.5.3.tgz#bdb1eeed5db70bcd1f68cd1e8c859834f0e6bc67"
- integrity sha512-9xWGu3ArKsjf6+WVrNoCUywybTB3rIidpvOI2tByQpzYVOupFUv6qohyrjDrVvPb6XHJQTD0NIzisR7RKhiP7A==
+"@fontsource/source-sans-pro@4.5.4":
+ version "4.5.4"
+ resolved "https://registry.yarnpkg.com/@fontsource/source-sans-pro/-/source-sans-pro-4.5.4.tgz#51510723ff40f446c7800f133e9ae604ae2f38d7"
+ integrity sha512-+YYw6HRvH9wYE+U2Hvxyossg+MHPApAj7VIjEqaXenNeNQa4U3uPD0e7pc+1Gic3srCQATN15O3S9WSFLXTmwQ==
"@gatsbyjs/parcel-namer-relative-to-cwd@0.0.2":
version "0.0.2"
@@ -2195,6 +2195,19 @@
escape-string-regexp "^2.0.0"
lodash.deburr "^4.1.0"
+"@skynetlabs/tus-js-client@^2.3.0":
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/@skynetlabs/tus-js-client/-/tus-js-client-2.3.0.tgz#a14fd4197e2bc4ce8be724967a0e4c17d937cb64"
+ integrity sha512-piGvPlJh+Bu3Qf08bDlc/TnFLXE81KnFoPgvnsddNwTSLyyspxPFxJmHO5ki6SYyOl3HmUtGPoix+r2M2UpFEA==
+ dependencies:
+ buffer-from "^0.1.1"
+ combine-errors "^3.0.3"
+ is-stream "^2.0.0"
+ js-base64 "^2.6.1"
+ lodash.throttle "^4.1.1"
+ proper-lockfile "^2.0.1"
+ url-parse "^1.4.3"
+
"@svgr/babel-plugin-add-jsx-attribute@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.0.0.tgz#bd6d1ff32a31b82b601e73672a789cc41e84fe18"
@@ -3167,6 +3180,13 @@ async-cache@^1.1.0:
dependencies:
lru-cache "^4.0.0"
+async-mutex@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df"
+ integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA==
+ dependencies:
+ tslib "^2.3.1"
+
async-retry-ng@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/async-retry-ng/-/async-retry-ng-2.0.1.tgz#f5285ec1c52654a2ba6a505d0c18b1eadfaebd41"
@@ -3236,12 +3256,12 @@ axios@^0.21.1:
dependencies:
follow-redirects "^1.14.0"
-axios@^0.24.0:
- version "0.24.0"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
- integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
+axios@^0.26.0:
+ version "0.26.1"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
+ integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
- follow-redirects "^1.14.4"
+ follow-redirects "^1.14.8"
axobject-query@^2.2.0:
version "2.2.0"
@@ -3319,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.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-4.9.0.tgz#5804094466f12670e3e43434addb80a3561d96d6"
- integrity sha512-q3xS5KDPoxujHrNWbilChE0Q+riCdxmvUseZbSzaikfY+KV9z3nCzaGkuEHxU2xVVa+8K5Nvu9zKlf/KtQfxXw==
+babel-plugin-remove-graphql-queries@^4.9.0, babel-plugin-remove-graphql-queries@^4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-remove-graphql-queries/-/babel-plugin-remove-graphql-queries-4.9.1.tgz#75290c6dd840d28343dc47f9517a634e02255b80"
+ integrity sha512-Mg+NB34cjdV6rIGIahMe0qij3KpWf7M8NFe8J1w2kxjQty4mpGX2qqmMUHhwxqwVWAhH1LZeiqitFZ6D/+CbJg==
dependencies:
"@babel/runtime" "^7.15.4"
- gatsby-core-utils "^3.9.0"
+ gatsby-core-utils "^3.9.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.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/babel-preset-gatsby/-/babel-preset-gatsby-2.9.0.tgz#6a478ee39b99be21b5ed804ebd8cc96a0fd492d3"
- integrity sha512-naGwVr1uCX2NsyM38pTp0f+vO0UfCH2h7wEC1H8B748twHTUpoOPJ/GWUs+/FQzNrfgOvHSspcqkRyD3Vb2EPg==
+babel-preset-gatsby@^2.9.1:
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/babel-preset-gatsby/-/babel-preset-gatsby-2.9.1.tgz#2f62f7f7899ed7282f0a84ef0b5a0e131225ed96"
+ integrity sha512-HkZyo5Phb5+vbICx0Q8Goj+FV8xPH4detCqJUDHH9sfBvAjvdnKfL2dtDFd0QvKhUQ/55rO3Rdcmo6PU5zYwZw==
dependencies:
"@babel/plugin-proposal-class-properties" "^7.14.0"
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.5"
@@ -3350,7 +3370,7 @@ babel-preset-gatsby@^2.9.0:
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.9.0"
+ gatsby-core-utils "^3.9.1"
gatsby-legacy-polyfills "^2.9.0"
backo2@^1.0.2, backo2@~1.0.2:
@@ -5985,7 +6005,7 @@ flatted@^3.1.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3"
integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==
-follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.4:
+follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
@@ -6147,10 +6167,10 @@ gatsby-background-image@1.6.0:
short-uuid "^4.2.0"
sort-media-queries "^0.2.2"
-gatsby-cli@^4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.9.0.tgz#3c6c9a26252671eb60abd399abdaca37836ab2a1"
- integrity sha512-e4pQ+7Z+9Cppql59fy0OOo2El+ERkzOCVW2+ev5CojiljDb4x/nUHIx9ahKhgA5136F0DaCZ6w/lrOWJmi3ZSQ==
+gatsby-cli@^4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.9.1.tgz#abe57cc656329deb69aef3d75b5cb14f36473d8e"
+ integrity sha512-iU5pmwAq5d1XXo98BkYe2KccH3Dy/jsj7QsvP0CpfzOO0EFtidg5KUzPPaekLaGyoqxiMwWf0uAX7S1ERzMFYw==
dependencies:
"@babel/code-frame" "^7.14.0"
"@babel/core" "^7.15.5"
@@ -6172,8 +6192,8 @@ gatsby-cli@^4.9.0:
execa "^5.1.1"
fs-exists-cached "^1.0.0"
fs-extra "^10.0.0"
- gatsby-core-utils "^3.9.0"
- gatsby-telemetry "^3.9.0"
+ gatsby-core-utils "^3.9.1"
+ gatsby-telemetry "^3.9.1"
hosted-git-info "^3.0.8"
is-valid-path "^0.1.1"
joi "^17.4.2"
@@ -6197,10 +6217,10 @@ gatsby-cli@^4.9.0:
yoga-layout-prebuilt "^1.10.0"
yurnalist "^2.1.0"
-gatsby-core-utils@^3.8.2, gatsby-core-utils@^3.9.0:
- version "3.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-core-utils/-/gatsby-core-utils-3.9.0.tgz#7be5969622e44c4475cb14f1ac64b49a4072ab6c"
- integrity sha512-SvPnr86oXTY3ldbQ4QAkEew3BQE9vlzUXcXVJqTOhMUeGEz2kibBFUmVp8ia9Y1eOD+K/0xXQ54jUqaResj69w==
+gatsby-core-utils@^3.8.2, gatsby-core-utils@^3.9.0, gatsby-core-utils@^3.9.1:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-core-utils/-/gatsby-core-utils-3.9.1.tgz#a4c1bb2021a7e7c06b4aad8d71c9c76ca9cdc21f"
+ integrity sha512-DNf7NhhH0WrFuoBvyURjsw4w+eKbp1GlRA0cchYHJwVTaDPvLvX1o7zxN76xIBx+m0kttpnO3KuJ9LDOSli3ag==
dependencies:
"@babel/runtime" "^7.15.4"
ci-info "2.0.0"
@@ -6233,26 +6253,26 @@ gatsby-legacy-polyfills@^2.9.0:
"@babel/runtime" "^7.15.4"
core-js-compat "3.9.0"
-gatsby-link@^4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-link/-/gatsby-link-4.9.0.tgz#3aac564fbc406d550d8654ffad116f200f34a9c1"
- integrity sha512-5QiqDQo16b8VwEx4mRkAnPhAt+fyWOTT+sz2AkG7enR7psoQ7fg9Ndck0CHfME9aLQFm/2GLw8JwD1OGyARPRw==
+gatsby-link@^4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-link/-/gatsby-link-4.9.1.tgz#af444bd3a28e90816a1291f85b70de920c302f1d"
+ integrity sha512-c5YbR43fESNKlScS+ciJaLjuJuFruoL1my9z6k0ZbMIJUI7z6qH+XIVteH00Kbryz0fejNDaGWeAr3gvEyDlSA==
dependencies:
"@babel/runtime" "^7.15.4"
"@types/reach__router" "^1.3.10"
- gatsby-page-utils "^2.9.0"
+ gatsby-page-utils "^2.9.1"
prop-types "^15.7.2"
-gatsby-page-utils@^2.9.0:
- version "2.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-page-utils/-/gatsby-page-utils-2.9.0.tgz#ec836db457eed98bc3a46fef3aba8c3d9ecd1c29"
- integrity sha512-/WaS9FJismKPZfRsS2vIhVAd/1eGRe9+dmFZS7Rp6OUPGrELlen3V8h6msE2BpbkmaTf4eQbKgFdrTBkykkAyw==
+gatsby-page-utils@^2.9.1:
+ version "2.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-page-utils/-/gatsby-page-utils-2.9.1.tgz#4da1fd5bb21623334868a9b77976755364ad03ed"
+ integrity sha512-Otgwt30usTa94pWF3915+w/6uCPJIQHSVEAF8BD9iBl2mCBCZIS0+rCrVNm9lBrg+tc3JuezvIQsSycwaWnO5Q==
dependencies:
"@babel/runtime" "^7.15.4"
bluebird "^3.7.2"
chokidar "^3.5.2"
fs-exists-cached "^1.0.0"
- gatsby-core-utils "^3.9.0"
+ gatsby-core-utils "^3.9.1"
glob "^7.2.0"
lodash "^4.17.21"
micromatch "^4.0.4"
@@ -6299,31 +6319,31 @@ gatsby-plugin-image@2.9.0:
objectFitPolyfill "^2.3.5"
prop-types "^15.7.2"
-gatsby-plugin-manifest@4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-plugin-manifest/-/gatsby-plugin-manifest-4.9.0.tgz#ddfd6e8d8597df7bdef07d527b08430508837ecb"
- integrity sha512-aRoY9pan+7rR1SGoGcm1039A9tWy0w2oS+s7GmNJqqBHJqN2JZUlvJrqjU4ZdyoZ1/DXx9zuzUQDzgiEBFJ2xw==
+gatsby-plugin-manifest@4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-plugin-manifest/-/gatsby-plugin-manifest-4.9.1.tgz#20513c7d942b424795b802506893c0f909c69b7a"
+ integrity sha512-Fye2vr7ioc7ETVKdCfpbc5ByU28+EB7ocqSORbazPgAT8OiPazpaBAYm98BONceuK3WaxGoEXMsmwmNBIIPjRA==
dependencies:
"@babel/runtime" "^7.15.4"
- gatsby-core-utils "^3.9.0"
+ gatsby-core-utils "^3.9.1"
gatsby-plugin-utils "^3.3.0"
semver "^7.3.5"
sharp "^0.30.1"
-gatsby-plugin-page-creator@^4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-plugin-page-creator/-/gatsby-plugin-page-creator-4.9.0.tgz#b0ddff77698f2d33f4789e0396e7788a2930fd70"
- integrity sha512-eryfrvg/d2L4oL6VR6FHQKX1gkRuVkqHe6gTLRBBk4B2+2x5UoxxSOSSxXlsTByFW0K3vyzD2nPpdeE08ArvEQ==
+gatsby-plugin-page-creator@^4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-plugin-page-creator/-/gatsby-plugin-page-creator-4.9.1.tgz#d73b6af0d26a19a25ddece1ea2276149f0ee2c44"
+ integrity sha512-06EA9nd+LxZxtxTsr6G8xYuaMCDZN2z0qEHX8TvQXcgFVktFB18nUISjfeMBTdiyM1zeVxMCWffBarbUG6IMGA==
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.9.0"
- gatsby-page-utils "^2.9.0"
+ gatsby-core-utils "^3.9.1"
+ gatsby-page-utils "^2.9.1"
gatsby-plugin-utils "^3.3.0"
- gatsby-telemetry "^3.9.0"
+ gatsby-telemetry "^3.9.1"
globby "^11.0.4"
lodash "^4.17.21"
@@ -6350,10 +6370,10 @@ gatsby-plugin-robots-txt@1.7.0:
"@babel/runtime" "^7.16.7"
generate-robotstxt "^8.0.3"
-gatsby-plugin-sharp@4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-4.9.0.tgz#c1370d00d55e025d2e5d1d9b3017865ea9d890c7"
- integrity sha512-65JcqL11kyecTDYl3uZ/SvBI2FRJgSC7JTxzoV1ZOJkNQpn4nRLRUFL5UTijnnwU9+tba3i6gxKtZEAlE94pcA==
+gatsby-plugin-sharp@4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-plugin-sharp/-/gatsby-plugin-sharp-4.9.1.tgz#44667f134be1855fe666ed58839bd280527337bd"
+ integrity sha512-oHnuxIok0Ct3nktn53XQFX36QXwa4H9hjj5lkxaY3zh0giYJmFAsHyvus6DKzGQ14cTC3AkvaD+rqv4SGdjRcg==
dependencies:
"@babel/runtime" "^7.15.4"
async "^3.2.3"
@@ -6361,9 +6381,9 @@ gatsby-plugin-sharp@4.9.0:
debug "^4.3.3"
filenamify "^4.3.0"
fs-extra "^10.0.0"
- gatsby-core-utils "^3.9.0"
+ gatsby-core-utils "^3.9.1"
gatsby-plugin-utils "^3.3.0"
- gatsby-telemetry "^3.9.0"
+ gatsby-telemetry "^3.9.1"
got "^11.8.3"
lodash "^4.17.21"
mini-svg-data-uri "^1.4.3"
@@ -6390,10 +6410,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.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-plugin-typescript/-/gatsby-plugin-typescript-4.9.0.tgz#e81031863b72c4471292759923325761a1e23c3a"
- integrity sha512-kY1kV5zGaPRbRAyHtbpPhxg/aW+6wqNhe6kA+H6EWCE+JcNZkKojyoIe+tKq3+ddq4ZE7BEDXU97N6t4WJFBvg==
+gatsby-plugin-typescript@^4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-plugin-typescript/-/gatsby-plugin-typescript-4.9.1.tgz#343f0cb6c4e72115875c264c127b293f8e1915a5"
+ integrity sha512-VYkosDqk4CLDz11snEdSIBSW/RAPi8eXD4fHyicuFx5dh11BGi7TMUzVVmwvYWHHleQdvboC4qYlDrzXXV++zw==
dependencies:
"@babel/core" "^7.15.5"
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.5"
@@ -6401,7 +6421,7 @@ gatsby-plugin-typescript@^4.9.0:
"@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.9.0"
+ babel-plugin-remove-graphql-queries "^4.9.1"
gatsby-plugin-utils@^3.3.0:
version "3.3.0"
@@ -6427,16 +6447,16 @@ gatsby-sharp@^0.3.0:
"@types/sharp" "^0.29.5"
sharp "^0.30.1"
-gatsby-source-filesystem@4.9.0:
- version "4.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-source-filesystem/-/gatsby-source-filesystem-4.9.0.tgz#8cf6f3f67cc97f8a75e284814f444a0eea3263e6"
- integrity sha512-woSxEgYeZSVZSpxm+FwB+RjRIyhcix1AR9766W4yk5RwYH2wciF2OfxRC73WW/o/v1ztzeW6RoqIIY+GBXaA1A==
+gatsby-source-filesystem@4.9.1:
+ version "4.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-source-filesystem/-/gatsby-source-filesystem-4.9.1.tgz#e619d8a482b0477c28225ffce9c28cbb0606ce67"
+ integrity sha512-2HS9+5i+F7tRgxBiv8Op9xK/jvd5DGUfedFsJ6/6sfoXUBddowvW4rVEj4XO42TsIQJe7eVj7FfzfqzSqQN8ow==
dependencies:
"@babel/runtime" "^7.15.4"
chokidar "^3.5.2"
file-type "^16.5.3"
fs-extra "^10.0.0"
- gatsby-core-utils "^3.9.0"
+ gatsby-core-utils "^3.9.1"
got "^9.6.0"
md5-file "^5.0.0"
mime "^2.5.2"
@@ -6445,10 +6465,10 @@ gatsby-source-filesystem@4.9.0:
valid-url "^1.0.9"
xstate "^4.26.1"
-gatsby-telemetry@^3.9.0:
- version "3.9.0"
- resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-3.9.0.tgz#d438841a6351ab1fd02c71244cdf78350694f070"
- integrity sha512-ifqJ4KS16mbpfZ5oVaU4WEbk6gccivVqjCbzfVGgqtl+C8B0u1CeShvr4NcJE1FdVFYIOB4uJeV9Wym03B075A==
+gatsby-telemetry@^3.9.1:
+ version "3.9.1"
+ resolved "https://registry.yarnpkg.com/gatsby-telemetry/-/gatsby-telemetry-3.9.1.tgz#3c20c7e0cb363ccaae41fb581ab289330b5d7f69"
+ integrity sha512-ChXTshfvo5njd/u6kSZErDUvc/uSmtOEuo7wrt/68Xjz2JVG6nsLlRxaZpx0DxnDAInouItMVX0VF40RAU7qKg==
dependencies:
"@babel/code-frame" "^7.14.0"
"@babel/runtime" "^7.15.4"
@@ -6458,7 +6478,7 @@ gatsby-telemetry@^3.9.0:
boxen "^4.2.0"
configstore "^5.0.1"
fs-extra "^10.0.0"
- gatsby-core-utils "^3.9.0"
+ gatsby-core-utils "^3.9.1"
git-up "^4.0.5"
is-docker "^2.2.1"
lodash "^4.17.21"
@@ -6496,10 +6516,10 @@ gatsby-worker@^1.9.0:
"@babel/core" "^7.15.5"
"@babel/runtime" "^7.15.4"
-gatsby@4.9.2:
- version "4.9.2"
- resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.9.2.tgz#2a31f8fe8b5007ecfed67b78af27af0587fee817"
- integrity sha512-dYUcCLZbyRIuzaswqxSzYFSGs+o1rdJFqZb6AODnq9jsMeDjN2jRC4DieDLzrWwDJCerW3rupDrHWsNfDj68Mw==
+gatsby@4.9.3:
+ version "4.9.3"
+ resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.9.3.tgz#a69a05a47048b6d140c89cfc05c3cb0e24f8a7aa"
+ integrity sha512-XZFmdW30vm1+s/kSxFFhMVl33u2qesWPdLEFtrQgtAnFiVjI/ukS/95gVOilhIMYyiTtuhQXiBKygkTl08oKFw==
dependencies:
"@babel/code-frame" "^7.14.0"
"@babel/core" "^7.15.5"
@@ -6526,8 +6546,8 @@ gatsby@4.9.2:
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.9.0"
- babel-preset-gatsby "^2.9.0"
+ babel-plugin-remove-graphql-queries "^4.9.1"
+ babel-preset-gatsby "^2.9.1"
better-opn "^2.1.1"
bluebird "^3.7.2"
body-parser "^1.19.0"
@@ -6570,18 +6590,18 @@ gatsby@4.9.2:
find-cache-dir "^3.3.2"
fs-exists-cached "1.0.0"
fs-extra "^10.0.0"
- gatsby-cli "^4.9.0"
- gatsby-core-utils "^3.9.0"
+ gatsby-cli "^4.9.1"
+ gatsby-core-utils "^3.9.1"
gatsby-graphiql-explorer "^2.9.0"
gatsby-legacy-polyfills "^2.9.0"
- gatsby-link "^4.9.0"
- gatsby-page-utils "^2.9.0"
+ gatsby-link "^4.9.1"
+ gatsby-page-utils "^2.9.1"
gatsby-parcel-config "^0.0.1"
- gatsby-plugin-page-creator "^4.9.0"
- gatsby-plugin-typescript "^4.9.0"
+ gatsby-plugin-page-creator "^4.9.1"
+ gatsby-plugin-typescript "^4.9.1"
gatsby-plugin-utils "^3.3.0"
gatsby-react-router-scroll "^5.9.0"
- gatsby-telemetry "^3.9.0"
+ gatsby-telemetry "^3.9.1"
gatsby-worker "^1.9.0"
glob "^7.2.0"
got "^11.8.2"
@@ -10960,12 +10980,14 @@ sjcl@^1.0.8:
resolved "https://registry.yarnpkg.com/sjcl/-/sjcl-1.0.8.tgz#f2ec8d7dc1f0f21b069b8914a41a8f236b0e252a"
integrity sha512-LzIjEQ0S0DpIgnxMEayM1rq9aGwGRG4OnZhCdjx7glTaJtf4zRfpg87ImfjSJjoW9vKpagd82McDOwbRT5kQKQ==
-skynet-js@4.0.23-beta:
- version "4.0.23-beta"
- resolved "https://registry.yarnpkg.com/skynet-js/-/skynet-js-4.0.23-beta.tgz#bfc8c6fa477f927d40213ec529392930edd04600"
- integrity sha512-5Hd8aOZM85sIjBT065Zf+korhQvCbA3Shm4DJnQT1kPlwvj+C+ZPPt/dwx1buzvDvfPmpXOhd377NskXzrFeDA==
+skynet-js@4.0.26-beta:
+ version "4.0.26-beta"
+ resolved "https://registry.yarnpkg.com/skynet-js/-/skynet-js-4.0.26-beta.tgz#5b6e924a0efa5fd6ee2c00760e1d4ce92d1ba0a9"
+ integrity sha512-YPqjNyqL6AhS9jMLyJ5PoilDZ7f2YFrqqhXUnzLBrjmWxICxcDeRu2GJh9MGCJUZ2Cv35IlG1ch4eiqFbs1wqA==
dependencies:
- axios "^0.24.0"
+ "@skynetlabs/tus-js-client" "^2.3.0"
+ async-mutex "^0.3.2"
+ axios "^0.26.0"
base32-decode "^1.0.0"
base32-encode "^1.1.1"
base64-js "^1.3.1"
@@ -10977,7 +10999,6 @@ skynet-js@4.0.23-beta:
randombytes "^2.1.0"
sjcl "^1.0.8"
skynet-mysky-utils "^0.3.0"
- tus-js-client "^2.2.0"
tweetnacl "^1.0.3"
url-join "^4.0.1"
url-parse "^1.5.1"
@@ -11881,7 +11902,7 @@ tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2, tslib@^2.0.3, tslib@^2.1.0, tslib@~2.3.0:
+tslib@^2, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1, tslib@~2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
@@ -11915,19 +11936,6 @@ tunnel-agent@^0.6.0:
dependencies:
safe-buffer "^5.0.1"
-tus-js-client@^2.2.0:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/tus-js-client/-/tus-js-client-2.3.1.tgz#cee9dcc9dbf3a7d9c1f8ca102ec5d75317465e36"
- integrity sha512-QEM7ySnthWT+wwePLTXVSQP8vBLCy0ZoJNDGFzNlsU+YVoK2WevIZwcRnKyo962xhYMiABe3aMvXvk4Ln+VRzQ==
- dependencies:
- buffer-from "^0.1.1"
- combine-errors "^3.0.3"
- is-stream "^2.0.0"
- js-base64 "^2.6.1"
- lodash.throttle "^4.1.1"
- proper-lockfile "^2.0.1"
- url-parse "^1.5.7"
-
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
version "0.14.5"
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
@@ -12157,7 +12165,7 @@ url-parse-lax@^3.0.0:
dependencies:
prepend-http "^2.0.0"
-url-parse@^1.5.1, url-parse@^1.5.7:
+url-parse@^1.4.3, url-parse@^1.5.1:
version "1.5.10"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
diff --git a/scripts/README.md b/scripts/README.md
index 2085eff7..e7b909b4 100644
--- a/scripts/README.md
+++ b/scripts/README.md
@@ -29,12 +29,6 @@ the health check.
The `portal-upgrade.sh` script upgrades the docker images for a portal and
clears and leftover images.
-**nginx-prune.sh**\
-The `nginx-prune.sh` script deletes all entries from nginx cache larger than
-the given size and smaller entries until nginx cache disk size is smaller than
-the given cache size limit. Both values are configured in
-`lib/nginx-prune-cache-subscript.sh`. The script doesn't require `sudo`.
-
## Webportal Upgrade Procedures
TODO...
diff --git a/scripts/blocklist-skylink.sh b/scripts/blocklist-skylink.sh
index ee19c9c2..cb60bbdb 100755
--- a/scripts/blocklist-skylink.sh
+++ b/scripts/blocklist-skylink.sh
@@ -34,18 +34,16 @@ else
skylinks=("$1") # just single skylink passed as input argument
fi
-# get local nginx ip adress
-nginx_ip=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx)
+# get local skyd ip adress
+ipaddress=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sia)
+
+# get sia api password either from env variable if exists or from apipassword file in sia-data directory
+apipassword=$(docker exec sia sh -c '[ ! -z "${SIA_API_PASSWORD}" ] && echo ${SIA_API_PASSWORD} || $(cat /sia-data/apipassword | tr -d '\n')')
# iterate over provided skylinks and block them one by one
for skylink in "${skylinks[@]}"; do
- printf "Blocking ${skylink} ... "
- status_code=$(curl --write-out '%{http_code}' --silent --output /dev/null --data "{\"add\":[\"$skylink\"]}" "http://${nginx_ip}:8000/skynet/blocklist")
+ echo "> Blocking ${skylink} ... "
- # print blocklist response status code
- if [ $status_code = "204" ]; then
- echo "done"
- else
- echo "error $status_code"
- fi
+ # POST /skynet/blocklist always returns 200 and in case of failure print error message
+ curl -A Sia-Agent -u "":${apipassword} --data "{\"add\":[\"$skylink\"]}" "http://${ipaddress}:9980/skynet/blocklist"
done
diff --git a/scripts/lib/nginx-prune-cache-subscript.sh b/scripts/lib/nginx-prune-cache-subscript.sh
deleted file mode 100755
index 99edb899..00000000
--- a/scripts/lib/nginx-prune-cache-subscript.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/local/bin/bash
-
-# This subscript is expected to be run inside docker container using 'bash'
-# image. The image is based on Alpine Linux. It's tools (find, stat, awk, sort)
-# are non-standard versions from BusyBox.
-
-MAX_CACHE_DIR_SIZE=20000000000
-MAX_KEEP_FILE_SIZE=1000000000
-
-total=0
-
-# We sort files by time, newest files are first. Format is:
-# time (last modification as seconds since Epoch), filepath, size (bytes)
-find /home/user/skynet-webportal/docker/data/nginx/cache -type f -exec stat -c "%Y %n %s" {} + | sort -rgk1 | while read line
-do
- size=$(echo $line | awk '{print $3}')
- new_total=$(($total + $size))
-
- # We always delete all files larger than MAX_KEEP_FILE_SIZE.
- # We keep all files smaller than MAX_KEEP_FILE_SIZE when cache size is
- # below MAX_CACHE_DIR_SIZE, then we delete also smaller files.
- if (("$size" <= "$MAX_KEEP_FILE_SIZE" && "$new_total" < "$MAX_CACHE_DIR_SIZE"))
- then
- total=$new_total
- continue
- fi
-
- filename=$(echo $line | awk '{print $2}')
- rm $filename
-done
diff --git a/scripts/nginx-prune.sh b/scripts/nginx-prune.sh
deleted file mode 100755
index ea5a1384..00000000
--- a/scripts/nginx-prune.sh
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/bin/bash
-
-# We execute the nginx cache pruning subscript from docker container so that we
-# can run the pruning script in user crontab without sudo.
-docker run --rm -v /home/user:/home/user bash /home/user/skynet-webportal/scripts/lib/nginx-prune-cache-subscript.sh
-
-# Some cache files are deleted, but are kept open, we hot reload nginx to get
-# them closed and removed from filesystem.
-docker exec nginx nginx -s reload
diff --git a/setup-scripts/blocklist-airtable.py b/setup-scripts/blocklist-airtable.py
index 09711605..8bd9d2dc 100755
--- a/setup-scripts/blocklist-airtable.py
+++ b/setup-scripts/blocklist-airtable.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
-from bot_utils import setup, send_msg
+from bot_utils import get_api_password, setup, send_msg
from random import randint
from time import sleep
@@ -11,6 +11,8 @@ import asyncio
import requests
import json
+from requests.auth import HTTPBasicAuth
+
setup()
@@ -38,14 +40,14 @@ def exec(command):
async def block_skylinks_from_airtable():
- # Get nginx's IP before doing anything else. If this step fails we don't
+ # Get sia IP before doing anything else. If this step fails we don't
# need to continue with the execution of the script.
ipaddress = exec(
- "docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx"
+ "docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sia"
)
if ipaddress == "":
- print("Nginx's IP could not be detected. Exiting.")
+ print("Skyd IP could not be detected. Exiting.")
return
print("Pulling blocked skylinks from Airtable via api integration")
@@ -117,11 +119,13 @@ async def block_skylinks_from_airtable():
print(
"Sending /skynet/blocklist request with "
+ str(len(skylinks))
- + " skylinks to siad through nginx"
+ + " skylinks to siad"
)
response = requests.post(
- "http://" + ipaddress + ":8000/skynet/blocklist",
+ "http://" + ipaddress + ":9980/skynet/blocklist",
data=json.dumps({"add": skylinks}),
+ headers={"User-Agent": "Sia-Agent"},
+ auth=HTTPBasicAuth("", get_api_password()),
)
if response.status_code != 200:
@@ -153,5 +157,5 @@ loop.run_until_complete(run_checks())
# --- BASH EQUIVALENT
# skylinks=$(curl "https://api.airtable.com/v0/${AIRTABLE_BASE}/${AIRTABLE_TABLE}?fields%5B%5D=${AIRTABLE_FIELD}" -H "Authorization: Bearer ${AIRTABLE_KEY}" | python3 -c "import sys, json; print('[\"' + '\",\"'.join([entry['fields']['Link'] for entry in json.load(sys.stdin)['records']]) + '\"]')")
-# ipaddress=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nginx)
+# ipaddress=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sia)
# curl --data "{\"add\" : ${skylinks}}" "${ipaddress}:8000/skynet/blocklist"
diff --git a/setup-scripts/support/authorized_keys b/setup-scripts/support/authorized_keys
index 6ee7b264..43698a94 100644
--- a/setup-scripts/support/authorized_keys
+++ b/setup-scripts/support/authorized_keys
@@ -8,3 +8,4 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPM43lzbKjFLChe5rKETxDpWpNlqXCGTBPiWlDN2vlLD
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN6Kcx8yetova4/ALUQHigo/PBMJO33ZTKOsg2jxSO2a user@deploy.siasky.dev
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDcenWnMQ6q/OEC4ZmQgjLDV2obWlR3fENV0zRGFvJF+ marcins@siasky.net
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB7prtVOTwtcSN9HkXum107RwcW5H8Vggx6Qv7T57ItT daniel@siasky.net
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII85HxoIRbPyr+xUjpuFUlQNW7smCNdIcmx2XgpmXnB0 marissa@skynetlabs.com
diff --git a/setup-scripts/support/crontab b/setup-scripts/support/crontab
index 2181d09c..7fffdfb9 100644
--- a/setup-scripts/support/crontab
+++ b/setup-scripts/support/crontab
@@ -5,4 +5,3 @@
44 5 * * * /home/user/skynet-webportal/scripts/backup-aws-s3.sh 1>>/home/user/skynet-webportal/logs/backup-aws-s3.log 2>>/home/user/skynet-webportal/logs/backup-aws-s3.log
6 13 * * * /home/user/skynet-webportal/scripts/db_backup.sh 1>>/home/user/skynet-webportal/logs/db_backup.log 2>>/home/user/skynet-webportal/logs/db_backup.log
0 5 * * * /home/user/skynet-webportal/scripts/es_cleaner.py 1 http://localhost:9200
-15 * * * * /home/user/skynet-webportal/scripts/nginx-prune.sh