Merge branch 'master' into portal-latest
This commit is contained in:
commit
752f4bbeb4
|
@ -5,8 +5,8 @@
|
||||||
Latest Skynet Webportal setup documentation and the setup process Skynet Labs
|
Latest Skynet Webportal setup documentation and the setup process Skynet Labs
|
||||||
supports is located at https://docs.siasky.net/webportal-management/overview.
|
supports is located at https://docs.siasky.net/webportal-management/overview.
|
||||||
|
|
||||||
Some of the scripts and setup documentation contained in this repository
|
Some scripts and setup documentation contained in this repository
|
||||||
(`skynet-webportal`) can be outdated and generally should not be used.
|
(`skynet-webportal`) may be outdated and generally should not be used.
|
||||||
|
|
||||||
## Web application
|
## Web application
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ For the purposes of complying with our code license, you can use the following S
|
||||||
`fb6c9320bc7e01fbb9cd8d8c3caaa371386928793c736837832e634aaaa484650a3177d6714a`
|
`fb6c9320bc7e01fbb9cd8d8c3caaa371386928793c736837832e634aaaa484650a3177d6714a`
|
||||||
|
|
||||||
## Running a Portal
|
## Running a Portal
|
||||||
For those interested in running a Webportal, head over to our developer docs [here](https://docs.siasky.net/webportal-management/overview.) to learn more.
|
For those interested in running a Webportal, head over to our developer docs [here](https://portal-docs.skynetlabs.com/) to learn more.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ location / {
|
||||||
set $path $uri;
|
set $path $uri;
|
||||||
|
|
||||||
rewrite_by_lua_block {
|
rewrite_by_lua_block {
|
||||||
|
local cjson = require("cjson")
|
||||||
local cache = ngx.shared.dnslink
|
local cache = ngx.shared.dnslink
|
||||||
local cache_value = cache:get(ngx.var.host)
|
local cache_value = cache:get(ngx.var.host)
|
||||||
|
|
||||||
|
@ -28,13 +29,23 @@ location / {
|
||||||
ngx.exit(ngx.status)
|
ngx.exit(ngx.status)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
ngx.var.skylink = res.body
|
local resolved = cjson.decode(res.body)
|
||||||
|
|
||||||
|
ngx.var.skylink = resolved.skylink
|
||||||
|
if resolved.sponsor then
|
||||||
|
ngx.req.set_header("Skynet-Api-Key", resolved.sponsor)
|
||||||
|
end
|
||||||
|
|
||||||
local cache_ttl = 300 -- 5 minutes cache expire time
|
local cache_ttl = 300 -- 5 minutes cache expire time
|
||||||
cache:set(ngx.var.host, ngx.var.skylink, cache_ttl)
|
cache:set(ngx.var.host, res.body, cache_ttl)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
ngx.var.skylink = cache_value
|
local resolved = cjson.decode(cache_value)
|
||||||
|
|
||||||
|
ngx.var.skylink = resolved.skylink
|
||||||
|
if resolved.sponsor then
|
||||||
|
ngx.req.set_header("Skynet-Api-Key", resolved.sponsor)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ngx.var.skylink = require("skynet.skylink").parse(ngx.var.skylink)
|
ngx.var.skylink = require("skynet.skylink").parse(ngx.var.skylink)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { SWRConfig } from "swr";
|
||||||
import "@fontsource/sora/300.css"; // light
|
import "@fontsource/sora/300.css"; // light
|
||||||
import "@fontsource/sora/400.css"; // normal
|
import "@fontsource/sora/400.css"; // normal
|
||||||
import "@fontsource/sora/500.css"; // medium
|
import "@fontsource/sora/500.css"; // medium
|
||||||
|
@ -6,6 +7,7 @@ import "@fontsource/sora/600.css"; // semibold
|
||||||
import "@fontsource/source-sans-pro/400.css"; // normal
|
import "@fontsource/source-sans-pro/400.css"; // normal
|
||||||
import "@fontsource/source-sans-pro/600.css"; // semibold
|
import "@fontsource/source-sans-pro/600.css"; // semibold
|
||||||
import "./src/styles/global.css";
|
import "./src/styles/global.css";
|
||||||
|
import swrConfig from "./src/lib/swrConfig";
|
||||||
import { MODAL_ROOT_ID } from "./src/components/Modal";
|
import { MODAL_ROOT_ID } from "./src/components/Modal";
|
||||||
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
|
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
|
||||||
|
|
||||||
|
@ -13,10 +15,12 @@ export function wrapPageElement({ element, props }) {
|
||||||
const Layout = element.type.Layout ?? React.Fragment;
|
const Layout = element.type.Layout ?? React.Fragment;
|
||||||
return (
|
return (
|
||||||
<PortalSettingsProvider>
|
<PortalSettingsProvider>
|
||||||
|
<SWRConfig value={swrConfig}>
|
||||||
<Layout {...props}>
|
<Layout {...props}>
|
||||||
{element}
|
{element}
|
||||||
<div id={MODAL_ROOT_ID} />
|
<div id={MODAL_ROOT_ID} />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
</SWRConfig>
|
||||||
</PortalSettingsProvider>
|
</PortalSettingsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { SWRConfig } from "swr";
|
||||||
import "@fontsource/sora/300.css"; // light
|
import "@fontsource/sora/300.css"; // light
|
||||||
import "@fontsource/sora/400.css"; // normal
|
import "@fontsource/sora/400.css"; // normal
|
||||||
import "@fontsource/sora/500.css"; // medium
|
import "@fontsource/sora/500.css"; // medium
|
||||||
|
@ -6,6 +7,7 @@ import "@fontsource/sora/600.css"; // semibold
|
||||||
import "@fontsource/source-sans-pro/400.css"; // normal
|
import "@fontsource/source-sans-pro/400.css"; // normal
|
||||||
import "@fontsource/source-sans-pro/600.css"; // semibold
|
import "@fontsource/source-sans-pro/600.css"; // semibold
|
||||||
import "./src/styles/global.css";
|
import "./src/styles/global.css";
|
||||||
|
import swrConfig from "./src/lib/swrConfig";
|
||||||
import { MODAL_ROOT_ID } from "./src/components/Modal";
|
import { MODAL_ROOT_ID } from "./src/components/Modal";
|
||||||
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
|
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
|
||||||
|
|
||||||
|
@ -13,10 +15,12 @@ export function wrapPageElement({ element, props }) {
|
||||||
const Layout = element.type.Layout ?? React.Fragment;
|
const Layout = element.type.Layout ?? React.Fragment;
|
||||||
return (
|
return (
|
||||||
<PortalSettingsProvider>
|
<PortalSettingsProvider>
|
||||||
|
<SWRConfig value={swrConfig}>
|
||||||
<Layout {...props}>
|
<Layout {...props}>
|
||||||
{element}
|
{element}
|
||||||
<div id={MODAL_ROOT_ID} />
|
<div id={MODAL_ROOT_ID} />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
</SWRConfig>
|
||||||
</PortalSettingsProvider>
|
</PortalSettingsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,13 @@ const BarTip = styled.span.attrs({
|
||||||
})``;
|
})``;
|
||||||
|
|
||||||
const BarLabel = styled.span.attrs({
|
const BarLabel = styled.span.attrs({
|
||||||
className: "bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow",
|
className: "usage-label bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow",
|
||||||
})`
|
})`
|
||||||
right: max(0%, ${({ $percentage }) => 100 - $percentage}%);
|
${({ $percentage }) => `
|
||||||
|
left: max(0%, ${$percentage}%);
|
||||||
top: -0.5rem;
|
top: -0.5rem;
|
||||||
transform: translateX(50%);
|
transform: translateX(-${$percentage}%);
|
||||||
|
`}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const GraphBar = ({ value, limit, label }) => {
|
export const GraphBar = ({ value, limit, label }) => {
|
||||||
|
|
|
@ -7,13 +7,10 @@ import { ChevronDownIcon } from "../Icons";
|
||||||
|
|
||||||
const dropDown = keyframes`
|
const dropDown = keyframes`
|
||||||
0% {
|
0% {
|
||||||
transform: scaleY(0);
|
transform: rotateX(-90deg);
|
||||||
}
|
|
||||||
80% {
|
|
||||||
transform: scaleY(1.1);
|
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: scaleY(1);
|
transform: rotateX(0deg);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -35,10 +32,11 @@ const Flyout = styled.div.attrs(({ open }) => ({
|
||||||
bg-white shadow-md shadow-palette-200/50
|
bg-white shadow-md shadow-palette-200/50
|
||||||
${open ? "visible" : "invisible"}`,
|
${open ? "visible" : "invisible"}`,
|
||||||
}))`
|
}))`
|
||||||
|
transform-origin: top center;
|
||||||
animation: ${({ open }) =>
|
animation: ${({ open }) =>
|
||||||
open
|
open
|
||||||
? css`
|
? css`
|
||||||
${dropDown} 0.1s ease-in-out
|
${dropDown} .15s ease-in-out forwards;
|
||||||
`
|
`
|
||||||
: "none"};
|
: "none"};
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -4,6 +4,7 @@ import useSWR from "swr";
|
||||||
import { Table, TableBody, TableCell, TableRow } from "../Table";
|
import { Table, TableBody, TableCell, TableRow } from "../Table";
|
||||||
import { ContainerLoadingIndicator } from "../LoadingIndicator";
|
import { ContainerLoadingIndicator } from "../LoadingIndicator";
|
||||||
|
|
||||||
|
import { ViewAllLink } from "./ViewAllLink";
|
||||||
import useFormattedActivityData from "./useFormattedActivityData";
|
import useFormattedActivityData from "./useFormattedActivityData";
|
||||||
|
|
||||||
export default function ActivityTable({ type }) {
|
export default function ActivityTable({ type }) {
|
||||||
|
@ -22,6 +23,7 @@ export default function ActivityTable({ type }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Table style={{ tableLayout: "fixed" }}>
|
<Table style={{ tableLayout: "fixed" }}>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{items.map(({ id, name, type, size, date, skylink }) => (
|
{items.map(({ id, name, type, size, date, skylink }) => (
|
||||||
|
@ -37,5 +39,7 @@ export default function ActivityTable({ type }) {
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
<ViewAllLink to={`/files?tab=${type}`} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,10 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Link } from "gatsby";
|
|
||||||
|
|
||||||
import { Panel } from "../Panel";
|
import { Panel } from "../Panel";
|
||||||
import { Tab, TabPanel, Tabs } from "../Tabs";
|
import { Tab, TabPanel, Tabs } from "../Tabs";
|
||||||
import { ArrowRightIcon } from "../Icons";
|
|
||||||
|
|
||||||
import ActivityTable from "./ActivityTable";
|
import ActivityTable from "./ActivityTable";
|
||||||
|
|
||||||
const ViewAllLink = (props) => (
|
|
||||||
<Link className="inline-flex mt-6 items-center gap-3 ease-in-out hover:brightness-90" {...props}>
|
|
||||||
<span className="bg-primary rounded-full w-[32px] h-[32px] inline-flex justify-center items-center">
|
|
||||||
<ArrowRightIcon />
|
|
||||||
</span>
|
|
||||||
<span className="font-sans text-xs uppercase text-palette-400">View all</span>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function LatestActivity() {
|
export default function LatestActivity() {
|
||||||
return (
|
return (
|
||||||
<Panel title="Latest activity">
|
<Panel title="Latest activity">
|
||||||
|
@ -24,11 +13,9 @@ export default function LatestActivity() {
|
||||||
<Tab id="downloads" title="Downloads" />
|
<Tab id="downloads" title="Downloads" />
|
||||||
<TabPanel tabId="uploads" className="pt-4">
|
<TabPanel tabId="uploads" className="pt-4">
|
||||||
<ActivityTable type="uploads" />
|
<ActivityTable type="uploads" />
|
||||||
<ViewAllLink to="/files?tab=uploads" />
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel tabId="downloads" className="pt-4">
|
<TabPanel tabId="downloads" className="pt-4">
|
||||||
<ActivityTable type="downloads" />
|
<ActivityTable type="downloads" />
|
||||||
<ViewAllLink to="/files?tab=downloads" />
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Link } from "gatsby";
|
||||||
|
|
||||||
|
import { ArrowRightIcon } from "../Icons";
|
||||||
|
|
||||||
|
export const ViewAllLink = (props) => (
|
||||||
|
<Link className="inline-flex mt-6 items-center gap-3 ease-in-out hover:brightness-90" {...props}>
|
||||||
|
<span className="bg-primary rounded-full w-[32px] h-[32px] inline-flex justify-center items-center">
|
||||||
|
<ArrowRightIcon />
|
||||||
|
</span>
|
||||||
|
<span className="font-sans text-xs uppercase text-palette-400">View all</span>
|
||||||
|
</Link>
|
||||||
|
);
|
|
@ -97,7 +97,7 @@ export const NavBar = () => {
|
||||||
as="button"
|
as="button"
|
||||||
onClick={onLogout}
|
onClick={onLogout}
|
||||||
activeClassName="text-primary"
|
activeClassName="text-primary"
|
||||||
className="cursor-pointer"
|
className="cursor-pointer w-full"
|
||||||
icon={LockClosedIcon}
|
icon={LockClosedIcon}
|
||||||
label="Log out"
|
label="Log out"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -109,7 +109,6 @@ export default function UploaderItem({ onUploadStateChange, upload }) {
|
||||||
{upload.status === "uploading" && (
|
{upload.status === "uploading" && (
|
||||||
<span className="uppercase tabular-nums">{Math.floor(upload.progress * 100)}%</span>
|
<span className="uppercase tabular-nums">{Math.floor(upload.progress * 100)}%</span>
|
||||||
)}
|
)}
|
||||||
{upload.status === "processing" && <span className="uppercase text-palette-300">Wait</span>}
|
|
||||||
{upload.status === "complete" && (
|
{upload.status === "complete" && (
|
||||||
<button
|
<button
|
||||||
className="uppercase hover:text-primary transition-colors duration-200"
|
className="uppercase hover:text-primary transition-colors duration-200"
|
||||||
|
|
|
@ -82,7 +82,7 @@ export const LoginForm = ({ onSuccess }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-center mt-8">
|
<p className="text-sm text-center mt-8">
|
||||||
Don't have an account? <HighlightedLink to="/auth/signup">Sign up</HighlightedLink>
|
Don't have an account? <HighlightedLink to="/auth/registration">Sign up</HighlightedLink>
|
||||||
</p>
|
</p>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -16,7 +16,7 @@ const fetcher = async (path) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PortalSettingsProvider = ({ children }) => {
|
export const PortalSettingsProvider = ({ children }) => {
|
||||||
const { data, error } = useSWRImmutable("/__internal/do/not/use/accounts", fetcher);
|
const { data, error } = useSWRImmutable("__internal/do/not/use/accounts", fetcher);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [settings, setSettings] = useState(defaultSettings);
|
const [settings, setSettings] = useState(defaultSettings);
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,41 @@
|
||||||
|
import { navigate } from "gatsby";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import useSWRImmutable from "swr/immutable";
|
import useSWRImmutable from "swr/immutable";
|
||||||
|
|
||||||
|
import { UnauthorizedError } from "../../lib/swrConfig";
|
||||||
|
import { FullScreenLoadingIndicator } from "../../components/LoadingIndicator";
|
||||||
import { UserContext } from "./UserContext";
|
import { UserContext } from "./UserContext";
|
||||||
|
|
||||||
export const UserProvider = ({ children }) => {
|
export const UserProvider = ({ children, allowGuests = false, allowAuthenticated = true }) => {
|
||||||
const { data: user, error, mutate } = useSWRImmutable("user");
|
const { data: user, error, mutate } = useSWRImmutable("user");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user || error) {
|
const guard = async () => {
|
||||||
|
if (user) {
|
||||||
|
if (!allowAuthenticated) {
|
||||||
|
navigate("/");
|
||||||
|
} else {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [user, error]);
|
} else if (error) {
|
||||||
|
if (error instanceof UnauthorizedError && !allowGuests) {
|
||||||
|
await navigate(`/auth/login?return_to=${encodeURIComponent(window.location.href)}`);
|
||||||
|
} else {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
} else if (user === null) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return <UserContext.Provider value={{ user, error, loading, mutate }}>{children}</UserContext.Provider>;
|
guard();
|
||||||
|
}, [user, error, allowGuests, allowAuthenticated]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserContext.Provider value={{ user, error, loading, mutate }}>
|
||||||
|
{loading && <FullScreenLoadingIndicator />}
|
||||||
|
{!loading && children}
|
||||||
|
</UserContext.Provider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { SWRConfig } from "swr";
|
|
||||||
|
|
||||||
import { UserProvider } from "../contexts/user";
|
import { UserProvider } from "../contexts/user";
|
||||||
import { guestsOnly, allUsers } from "../lib/swrConfig";
|
|
||||||
|
|
||||||
const Layout = styled.div.attrs({
|
const Layout = styled.div.attrs({
|
||||||
className: "min-h-screen w-screen bg-black flex",
|
className: "min-h-screen w-screen bg-black flex",
|
||||||
|
@ -22,12 +20,11 @@ const Content = styled.div.attrs({
|
||||||
})``;
|
})``;
|
||||||
|
|
||||||
const AuthLayout =
|
const AuthLayout =
|
||||||
(swrConfig) =>
|
(userProviderProps) =>
|
||||||
({ children }) => {
|
({ children }) =>
|
||||||
return (
|
(
|
||||||
<>
|
<>
|
||||||
<SWRConfig value={swrConfig}>
|
<UserProvider {...userProviderProps}>
|
||||||
<UserProvider>
|
|
||||||
<Layout>
|
<Layout>
|
||||||
<SloganContainer className="pl-20 pr-20 lg:pr-30 xl:pr-40">
|
<SloganContainer className="pl-20 pr-20 lg:pr-30 xl:pr-40">
|
||||||
<div className="">
|
<div className="">
|
||||||
|
@ -36,15 +33,26 @@ const AuthLayout =
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</SloganContainer>
|
</SloganContainer>
|
||||||
<Content>{children}</Content>
|
<Content>
|
||||||
|
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
||||||
|
<div className="mb-4 md:mb-16">
|
||||||
|
<img src="/images/logo-black-text.svg" alt="Skynet" className="-ml-2" />
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Content>
|
||||||
</Layout>
|
</Layout>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
</SWRConfig>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
// Some pages (e.g. email confirmation) need to be accessible to both logged-in and guest users.
|
// Some pages (e.g. email confirmation) need to be accessible to both logged-in and guest users.
|
||||||
export const AllUsersAuthLayout = AuthLayout(allUsers);
|
export const AllUsersAuthLayout = AuthLayout({
|
||||||
|
allowGuests: true,
|
||||||
|
allowAuthenticated: true,
|
||||||
|
});
|
||||||
|
|
||||||
export default AuthLayout(guestsOnly);
|
export default AuthLayout({
|
||||||
|
allowGuests: true,
|
||||||
|
allowAuthenticated: false,
|
||||||
|
});
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { SWRConfig } from "swr";
|
|
||||||
|
|
||||||
import { authenticatedOnly } from "../lib/swrConfig";
|
|
||||||
|
|
||||||
import { PageContainer } from "../components/PageContainer";
|
import { PageContainer } from "../components/PageContainer";
|
||||||
import { NavBar } from "../components/NavBar";
|
import { NavBar } from "../components/NavBar";
|
||||||
|
@ -30,22 +27,16 @@ const Layout = ({ children }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DashboardLayout = ({ children }) => {
|
const DashboardLayout = ({ children }) => (
|
||||||
return (
|
|
||||||
<>
|
<>
|
||||||
<SWRConfig value={authenticatedOnly}>
|
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
<Layout>
|
<Layout>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<PageContainer>
|
<PageContainer className="mt-2 md:mt-14">{children}</PageContainer>
|
||||||
<main className="mt-14">{children}</main>
|
|
||||||
</PageContainer>
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</Layout>
|
</Layout>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
</SWRConfig>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
export default DashboardLayout;
|
export default DashboardLayout;
|
||||||
|
|
|
@ -1,43 +1,12 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Link } from "gatsby";
|
import { Link } from "gatsby";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { SWRConfig } from "swr";
|
|
||||||
|
|
||||||
import { authenticatedOnly } from "../lib/swrConfig";
|
import DashboardLayout from "./DashboardLayout";
|
||||||
|
|
||||||
import { PageContainer } from "../components/PageContainer";
|
|
||||||
import { NavBar } from "../components/NavBar";
|
|
||||||
import { Footer } from "../components/Footer";
|
|
||||||
import { UserProvider, useUser } from "../contexts/user";
|
|
||||||
import { ContainerLoadingIndicator } from "../components/LoadingIndicator";
|
|
||||||
|
|
||||||
const Wrapper = styled.div.attrs({
|
|
||||||
className: "min-h-screen overflow-hidden",
|
|
||||||
})`
|
|
||||||
background-image: url(/images/dashboard-bg.svg);
|
|
||||||
background-position: center -280px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const Layout = ({ children }) => {
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
// Prevent from flashing the dashboard screen to unauthenticated users.
|
|
||||||
return (
|
|
||||||
<Wrapper>
|
|
||||||
{!user && (
|
|
||||||
<div className="fixed inset-0 flex justify-center items-center bg-palette-100/50">
|
|
||||||
<ContainerLoadingIndicator className="!text-palette-200/50" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{user && <>{children}</>}
|
|
||||||
</Wrapper>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Sidebar = () => (
|
const Sidebar = () => (
|
||||||
<aside className="w-full lg:w-48 bg-white text-sm font-sans font-light text-palette-600 shrink-0">
|
<aside className="w-full lg:w-48 text-sm font-sans font-light text-palette-600 shrink-0">
|
||||||
<nav>
|
<nav className="bg-white">
|
||||||
<SidebarLink activeClassName="!border-l-primary" to="/settings">
|
<SidebarLink activeClassName="!border-l-primary" to="/settings">
|
||||||
Account
|
Account
|
||||||
</SidebarLink>
|
</SidebarLink>
|
||||||
|
@ -55,7 +24,7 @@ const Sidebar = () => (
|
||||||
);
|
);
|
||||||
|
|
||||||
const SidebarLink = styled(Link).attrs({
|
const SidebarLink = styled(Link).attrs({
|
||||||
className: `h-12 py-3 px-6 h-full w-full flex
|
className: `h-12 py-3 px-6 w-full flex
|
||||||
border-l-2 border-l-palette-200
|
border-l-2 border-l-palette-200
|
||||||
border-b border-b-palette-100 last:border-b-transparent`,
|
border-b border-b-palette-100 last:border-b-transparent`,
|
||||||
})``;
|
})``;
|
||||||
|
@ -67,21 +36,13 @@ const Content = styled.main.attrs({
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const UserSettingsLayout = ({ children }) => (
|
const UserSettingsLayout = ({ children }) => (
|
||||||
<SWRConfig value={authenticatedOnly}>
|
<DashboardLayout>
|
||||||
<UserProvider>
|
|
||||||
<Layout>
|
|
||||||
<NavBar />
|
|
||||||
<PageContainer className="mt-2 md:mt-14">
|
|
||||||
<h6 className="hidden md:block mb-2 text-palette-400">Settings</h6>
|
<h6 className="hidden md:block mb-2 text-palette-400">Settings</h6>
|
||||||
<div className="flex flex-col lg:flex-row">
|
<div className="flex flex-col lg:flex-row">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<Content className="lg:w-settings-lg xl:w-settings-xl">{children}</Content>
|
<Content className="lg:w-settings-lg xl:w-settings-xl">{children}</Content>
|
||||||
</div>
|
</div>
|
||||||
</PageContainer>
|
</DashboardLayout>
|
||||||
<Footer />
|
|
||||||
</Layout>
|
|
||||||
</UserProvider>
|
|
||||||
</SWRConfig>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default UserSettingsLayout;
|
export default UserSettingsLayout;
|
||||||
|
|
|
@ -1,39 +1,22 @@
|
||||||
import { navigate } from "gatsby";
|
|
||||||
import { StatusCodes } from "http-status-codes";
|
import { StatusCodes } from "http-status-codes";
|
||||||
|
|
||||||
// TODO: portal-aware URL
|
export class UnauthorizedError extends Error {}
|
||||||
const baseUrl = process.env.NODE_ENV !== "production" ? "/api" : "https://account.skynetpro.net/api";
|
|
||||||
|
|
||||||
const redirectUnauthenticated = (key) =>
|
const config = {
|
||||||
fetch(`${baseUrl}/${key}`).then((response) => {
|
fetcher: (key) =>
|
||||||
if (response.status === StatusCodes.UNAUTHORIZED) {
|
fetch(`/api/${key}`).then(async (response) => {
|
||||||
navigate(`/auth/login?return_to=${encodeURIComponent(window.location.href)}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
});
|
|
||||||
|
|
||||||
const redirectAuthenticated = (key) =>
|
|
||||||
fetch(`${baseUrl}/${key}`).then(async (response) => {
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
await navigate("/");
|
|
||||||
return response.json();
|
return response.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there was an error, let's throw so useSWR's "error" property is populated instead "data".
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
throw new Error(data?.message || `Error occured when trying to fetch: ${key}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
export const allUsers = {
|
if (response.status === StatusCodes.UNAUTHORIZED) {
|
||||||
fetcher: (key) => fetch(`${baseUrl}/${key}`).then((response) => response.json()),
|
throw new UnauthorizedError(data?.message || "Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(data?.message || `Error occurred when trying to fetch: ${key}`);
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const authenticatedOnly = {
|
export default config;
|
||||||
fetcher: redirectUnauthenticated,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const guestsOnly = {
|
|
||||||
fetcher: redirectAuthenticated,
|
|
||||||
};
|
|
||||||
|
|
|
@ -22,16 +22,11 @@ const LoginPage = ({ location }) => {
|
||||||
<Metadata>
|
<Metadata>
|
||||||
<title>Sign In</title>
|
<title>Sign In</title>
|
||||||
</Metadata>
|
</Metadata>
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
|
||||||
<div className="mb-4 md:mb-16">
|
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
|
||||||
</div>
|
|
||||||
<LoginForm
|
<LoginForm
|
||||||
onSuccess={async () => {
|
onSuccess={async () => {
|
||||||
await refreshUserState();
|
await refreshUserState();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -61,11 +61,7 @@ const SignUpPage = () => {
|
||||||
<Metadata>
|
<Metadata>
|
||||||
<title>Sign Up</title>
|
<title>Sign Up</title>
|
||||||
</Metadata>
|
</Metadata>
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
<div className="flex flex-col">
|
||||||
<div className="mb-4 md:mb-16">
|
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{!settings.areAccountsEnabled && <Alert $variant="info">Accounts are not enabled on this portal.</Alert>}
|
{!settings.areAccountsEnabled && <Alert $variant="info">Accounts are not enabled on this portal.</Alert>}
|
||||||
|
|
||||||
{settings.areAccountsEnabled && (
|
{settings.areAccountsEnabled && (
|
||||||
|
@ -73,11 +69,17 @@ const SignUpPage = () => {
|
||||||
{settings.isSubscriptionRequired ? <PaidPortalHeader /> : <FreePortalHeader />}
|
{settings.isSubscriptionRequired ? <PaidPortalHeader /> : <FreePortalHeader />}
|
||||||
|
|
||||||
{state !== State.Success && (
|
{state !== State.Success && (
|
||||||
|
<>
|
||||||
<SignUpForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
<SignUpForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
||||||
|
|
||||||
|
<p className="text-sm text-center mt-8">
|
||||||
|
Already have an account? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{state === State.Success && (
|
{state === State.Success && (
|
||||||
<div className="text-center">
|
<div>
|
||||||
<p className="text-primary font-semibold">Please check your inbox and confirm your email address.</p>
|
<p className="text-primary font-semibold">Please check your inbox and confirm your email address.</p>
|
||||||
<p>You will be redirected to your dashboard shortly.</p>
|
<p>You will be redirected to your dashboard shortly.</p>
|
||||||
<HighlightedLink to="/">Click here to go there now.</HighlightedLink>
|
<HighlightedLink to="/">Click here to go there now.</HighlightedLink>
|
||||||
|
@ -89,10 +91,6 @@ const SignUpPage = () => {
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-sm text-center mt-8">
|
|
||||||
Already have an account? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</PlansProvider>
|
</PlansProvider>
|
||||||
);
|
);
|
|
@ -20,10 +20,6 @@ const ResetPasswordPage = () => {
|
||||||
<Metadata>
|
<Metadata>
|
||||||
<title>Reset Password</title>
|
<title>Reset Password</title>
|
||||||
</Metadata>
|
</Metadata>
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
|
||||||
<div className="mb-4 md:mb-16">
|
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
|
||||||
</div>
|
|
||||||
{state !== State.Success && (
|
{state !== State.Success && (
|
||||||
<RecoveryForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
<RecoveryForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
|
||||||
)}
|
)}
|
||||||
|
@ -41,10 +37,9 @@ const ResetPasswordPage = () => {
|
||||||
Suddenly remembered your password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
Suddenly remembered your password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Don't actually have an account? <HighlightedLink to="/auth/signup">Create one!</HighlightedLink>
|
Don't actually have an account? <HighlightedLink to="/auth/registration">Create one!</HighlightedLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -57,10 +57,6 @@ const EmailConfirmationPage = ({ location }) => {
|
||||||
<Metadata>
|
<Metadata>
|
||||||
<title>Confirm E-mail Address</title>
|
<title>Confirm E-mail Address</title>
|
||||||
</Metadata>
|
</Metadata>
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
|
||||||
<div className="mb-4 md:mb-16">
|
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{state === State.Pure && <p>Please wait while we verify your account...</p>}
|
{state === State.Pure && <p>Please wait while we verify your account...</p>}
|
||||||
|
|
||||||
|
@ -74,7 +70,6 @@ const EmailConfirmationPage = ({ location }) => {
|
||||||
|
|
||||||
{state === State.Failure && <p className="text-error">Something went wrong, please try again later.</p>}
|
{state === State.Failure && <p className="text-error">Something went wrong, please try again later.</p>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,10 +24,6 @@ const RecoverPage = ({ location }) => {
|
||||||
<Metadata>
|
<Metadata>
|
||||||
<title>Recover Your Account</title>
|
<title>Recover Your Account</title>
|
||||||
</Metadata>
|
</Metadata>
|
||||||
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
|
|
||||||
<div className="mb-4 md:mb-16">
|
|
||||||
<img src="/images/logo-black-text.svg" alt="Skynet" />
|
|
||||||
</div>
|
|
||||||
{state !== State.Success && (
|
{state !== State.Success && (
|
||||||
<ResetPasswordForm
|
<ResetPasswordForm
|
||||||
token={token}
|
token={token}
|
||||||
|
@ -52,7 +48,6 @@ const RecoverPage = ({ location }) => {
|
||||||
<p className="text-sm text-center mt-8">
|
<p className="text-sm text-center mt-8">
|
||||||
Suddenly remembered your old password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
Suddenly remembered your old password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -11242,9 +11242,9 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
|
||||||
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
|
||||||
|
|
||||||
moment@^2.29.1:
|
moment@^2.29.1:
|
||||||
version "2.29.1"
|
version "2.29.2"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
|
||||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
|
||||||
|
|
||||||
move-concurrently@^1.0.1:
|
move-concurrently@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/sora": "4.5.5",
|
"@fontsource/sora": "4.5.5",
|
||||||
"@fontsource/source-sans-pro": "4.5.6",
|
"@fontsource/source-sans-pro": "4.5.6",
|
||||||
"@stripe/react-stripe-js": "1.7.0",
|
"@stripe/react-stripe-js": "1.7.1",
|
||||||
"@stripe/stripe-js": "1.26.0",
|
"@stripe/stripe-js": "1.27.0",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.1",
|
||||||
"copy-text-to-clipboard": "^3.0.1",
|
"copy-text-to-clipboard": "^3.0.1",
|
||||||
"dayjs": "1.11.0",
|
"dayjs": "1.11.0",
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-toastify": "8.2.0",
|
"react-toastify": "8.2.0",
|
||||||
"skynet-js": "3.0.2",
|
"skynet-js": "3.0.2",
|
||||||
"stripe": "8.215.0",
|
"stripe": "8.216.0",
|
||||||
"swr": "1.2.2",
|
"swr": "1.2.2",
|
||||||
"yup": "0.32.11"
|
"yup": "0.32.11"
|
||||||
},
|
},
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
"@tailwindcss/forms": "0.5.0",
|
"@tailwindcss/forms": "0.5.0",
|
||||||
"@tailwindcss/typography": "0.5.2",
|
"@tailwindcss/typography": "0.5.2",
|
||||||
"autoprefixer": "10.4.4",
|
"autoprefixer": "10.4.4",
|
||||||
"eslint": "8.12.0",
|
"eslint": "8.13.0",
|
||||||
"eslint-config-next": "12.1.4",
|
"eslint-config-next": "12.1.4",
|
||||||
"postcss": "8.4.12",
|
"postcss": "8.4.12",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
|
|
|
@ -3,7 +3,14 @@ import { useFormik, getIn, setIn } from "formik";
|
||||||
import classnames from "classnames";
|
import classnames from "classnames";
|
||||||
import SelfServiceMessages from "./SelfServiceMessages";
|
import SelfServiceMessages from "./SelfServiceMessages";
|
||||||
|
|
||||||
export default function SelfServiceForm({ fieldsConfig, onSubmit, title, validationSchema = null, button = "Submit" }) {
|
export default function SelfServiceForm({
|
||||||
|
fieldsConfig,
|
||||||
|
onSubmit,
|
||||||
|
title,
|
||||||
|
onError,
|
||||||
|
validationSchema = null,
|
||||||
|
button = "Submit",
|
||||||
|
}) {
|
||||||
const [messages, setMessages] = React.useState([]);
|
const [messages, setMessages] = React.useState([]);
|
||||||
const fields = fieldsConfig.sort((a, b) => (a.position < b.position ? -1 : 1));
|
const fields = fieldsConfig.sort((a, b) => (a.position < b.position ? -1 : 1));
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
|
@ -21,6 +28,9 @@ export default function SelfServiceForm({ fieldsConfig, onSubmit, title, validat
|
||||||
const data = await error.response.json();
|
const data = await error.response.json();
|
||||||
|
|
||||||
if (data.message) {
|
if (data.message) {
|
||||||
|
if (typeof onError === "function") {
|
||||||
|
onError(data.message);
|
||||||
|
}
|
||||||
setMessages((messages) => [...messages, { type: "error", text: data.message }]);
|
setMessages((messages) => [...messages, { type: "error", text: data.message }]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -23,6 +23,8 @@ export default function Recovery() {
|
||||||
useAnonRoute(); // ensure user is not logged in
|
useAnonRoute(); // ensure user is not logged in
|
||||||
|
|
||||||
const [success, setSuccess] = React.useState(false);
|
const [success, setSuccess] = React.useState(false);
|
||||||
|
const [skynetFreeInviteVisible, setSkynetFreeInviteVisible] = React.useState(false);
|
||||||
|
const isSiaskyNet = typeof window !== "undefined" && window.location.hostname === "account.siasky.net";
|
||||||
|
|
||||||
const onSubmit = async (values) => {
|
const onSubmit = async (values) => {
|
||||||
await accountsApi.post("user/recover/request", {
|
await accountsApi.post("user/recover/request", {
|
||||||
|
@ -64,6 +66,16 @@ export default function Recovery() {
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
for a new account
|
for a new account
|
||||||
</p>
|
</p>
|
||||||
|
{skynetFreeInviteVisible && isSiaskyNet && (
|
||||||
|
<div className="font-content rounded border border-blue-200 mt-6 p-4 bg-blue-100">
|
||||||
|
<p>
|
||||||
|
All Siasky.net accounts have been moved to{" "}
|
||||||
|
<a className="text-primary" href="https://skynetfree.net">
|
||||||
|
SkynetFree.net
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!success && (
|
{!success && (
|
||||||
|
@ -72,6 +84,9 @@ export default function Recovery() {
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
button="Send recovery link"
|
button="Send recovery link"
|
||||||
|
onError={(errorMessage) =>
|
||||||
|
setSkynetFreeInviteVisible(errorMessage === "registrations are currently disabled")
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -175,17 +175,17 @@
|
||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64"
|
||||||
integrity sha512-ZK5v4bJwgXldAUA8r3q9YKfCwOqoHTK/ZqRjSeRXQrBXWouoPnS4MQtgC4AXGiiBuUu5wxrRgTlv0ktmM4P1Aw==
|
integrity sha512-ZK5v4bJwgXldAUA8r3q9YKfCwOqoHTK/ZqRjSeRXQrBXWouoPnS4MQtgC4AXGiiBuUu5wxrRgTlv0ktmM4P1Aw==
|
||||||
|
|
||||||
"@stripe/react-stripe-js@1.7.0":
|
"@stripe/react-stripe-js@1.7.1":
|
||||||
version "1.7.0"
|
version "1.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.0.tgz#83c993a09a903703205d556617f9729784a896c3"
|
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.1.tgz#6e1db8f4a0eaf2193b153173d4aa7c38b681310d"
|
||||||
integrity sha512-L20v8Jq0TDZFL2+y+uXD751t6q9SalSFkSYZpmZ2VWrGZGK7HAGfRQ804dzYSSr5fGenW6iz6y7U0YKfC/TK3g==
|
integrity sha512-GiUPoMo0xVvmpRD6JR9JAhAZ0W3ZpnYZNi0KE+91+tzrSFVpChKZbeSsJ5InlZhHFk9NckJCt1wOYBTqNsvt3A==
|
||||||
dependencies:
|
dependencies:
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
"@stripe/stripe-js@1.26.0":
|
"@stripe/stripe-js@1.27.0":
|
||||||
version "1.26.0"
|
version "1.27.0"
|
||||||
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.26.0.tgz#45670924753c01e18d0544ea1f1067b474aaa96f"
|
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.27.0.tgz#ab0c82fa89fd40260de4414f69868b769e810550"
|
||||||
integrity sha512-4R1vC75yKaCVFARW3bhelf9+dKt4NP4iZY/sIjGK7AAMBVvZ47eG74NvsAIUdUnhOXSWFMjdFWqv+etk5BDW4g==
|
integrity sha512-SEiybUBu+tlsFKuzdFFydxxjkbrdzHo0tz/naYC5Dt9or/Ux2gcKJBPYQ4RmqQCNHFxgyNj6UYsclywwhe2inQ==
|
||||||
|
|
||||||
"@tailwindcss/forms@0.5.0":
|
"@tailwindcss/forms@0.5.0":
|
||||||
version "0.5.0"
|
version "0.5.0"
|
||||||
|
@ -923,10 +923,10 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
|
||||||
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
|
||||||
|
|
||||||
eslint@8.12.0:
|
eslint@8.13.0:
|
||||||
version "8.12.0"
|
version "8.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.12.0.tgz#c7a5bd1cfa09079aae64c9076c07eada66a46e8e"
|
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7"
|
||||||
integrity sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==
|
integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@eslint/eslintrc" "^1.2.1"
|
"@eslint/eslintrc" "^1.2.1"
|
||||||
"@humanwhocodes/config-array" "^0.9.2"
|
"@humanwhocodes/config-array" "^0.9.2"
|
||||||
|
@ -1644,14 +1644,7 @@ mini-svg-data-uri@^1.2.3:
|
||||||
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz#91d2c09f45e056e5e1043340b8b37ba7b50f4fac"
|
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz#91d2c09f45e056e5e1043340b8b37ba7b50f4fac"
|
||||||
integrity sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==
|
integrity sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==
|
||||||
|
|
||||||
minimatch@^3.0.4:
|
minimatch@^3.0.4, minimatch@^3.1.2:
|
||||||
version "3.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
|
||||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
|
||||||
dependencies:
|
|
||||||
brace-expansion "^1.1.7"
|
|
||||||
|
|
||||||
minimatch@^3.1.2:
|
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||||
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
|
||||||
|
@ -1965,16 +1958,7 @@ pretty-bytes@6.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.0.0.tgz#928be2ad1f51a2e336add8ba764739f9776a8140"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.0.0.tgz#928be2ad1f51a2e336add8ba764739f9776a8140"
|
||||||
integrity sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==
|
integrity sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==
|
||||||
|
|
||||||
prop-types@^15.7.2:
|
prop-types@^15.7.2, prop-types@^15.8.1:
|
||||||
version "15.7.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
|
||||||
dependencies:
|
|
||||||
loose-envify "^1.4.0"
|
|
||||||
object-assign "^4.1.1"
|
|
||||||
react-is "^16.8.1"
|
|
||||||
|
|
||||||
prop-types@^15.8.1:
|
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
|
@ -2036,7 +2020,7 @@ react-fast-compare@^2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
|
||||||
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
|
||||||
|
|
||||||
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
|
react-is@^16.13.1, react-is@^16.7.0:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
@ -2264,10 +2248,10 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||||
|
|
||||||
stripe@8.215.0:
|
stripe@8.216.0:
|
||||||
version "8.215.0"
|
version "8.216.0"
|
||||||
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.215.0.tgz#bb464e256fb83da9ea2f514711fd0f6f7ae7dc9a"
|
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.216.0.tgz#23c047498526d13a238c3aca7b4dc8cbbd522e46"
|
||||||
integrity sha512-M+7iTZ9bzTkU1Ms+Zsuh0mTQfEzOjMoqyEaVBpuUmdbWTvshavzpAihsOkfabEu+sNY0vdbQxxHZ4kI3W8pKHQ==
|
integrity sha512-LY8cNGizEnklIa4T82l6mZW0HS4cfzo1hNuhT+ZR9PBkmYcSUbg3ilUBVF0FCd4RP+NA44VEVfoSTTZ1Gg5+rQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" ">=8.1.0"
|
"@types/node" ">=8.1.0"
|
||||||
qs "^6.10.3"
|
qs "^6.10.3"
|
||||||
|
|
|
@ -8,12 +8,14 @@ const port = Number(process.env.DNSLINK_API_PORT) || 3100;
|
||||||
const server = express();
|
const server = express();
|
||||||
|
|
||||||
const dnslinkNamespace = "skynet-ns";
|
const dnslinkNamespace = "skynet-ns";
|
||||||
|
const sponsorNamespace = "skynet-sponsor-key";
|
||||||
const dnslinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/.+$`);
|
const dnslinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/.+$`);
|
||||||
|
const sponsorRegExp = new RegExp(`^${sponsorNamespace}=[a-zA-Z0-9]+$`);
|
||||||
const dnslinkSkylinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/([a-zA-Z0-9_-]{46}|[a-z0-9]{55})`);
|
const dnslinkSkylinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/([a-zA-Z0-9_-]{46}|[a-z0-9]{55})`);
|
||||||
const hint = `valid example: dnslink=/${dnslinkNamespace}/3ACpC9Umme41zlWUgMQh1fw0sNwgWwyfDDhRQ9Sppz9hjQ`;
|
const hint = `valid example: dnslink=/${dnslinkNamespace}/3ACpC9Umme41zlWUgMQh1fw0sNwgWwyfDDhRQ9Sppz9hjQ`;
|
||||||
|
|
||||||
server.get("/dnslink/:name", async (req, res) => {
|
server.get("/dnslink/:name", async (req, res) => {
|
||||||
const success = (skylink) => res.send(skylink);
|
const success = (response) => res.json(response);
|
||||||
const failure = (message) => res.status(400).send(message);
|
const failure = (message) => res.status(400).send(message);
|
||||||
|
|
||||||
if (!isValidDomain(req.params.name)) {
|
if (!isValidDomain(req.params.name)) {
|
||||||
|
@ -22,7 +24,7 @@ server.get("/dnslink/:name", async (req, res) => {
|
||||||
|
|
||||||
const lookup = `_dnslink.${req.params.name}`;
|
const lookup = `_dnslink.${req.params.name}`;
|
||||||
|
|
||||||
dns.resolveTxt(lookup, (error, records) => {
|
dns.resolveTxt(lookup, (error, addresses) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error.code === "ENOTFOUND") {
|
if (error.code === "ENOTFOUND") {
|
||||||
return failure(`ENOTFOUND: ${lookup} TXT record doesn't exist`);
|
return failure(`ENOTFOUND: ${lookup} TXT record doesn't exist`);
|
||||||
|
@ -35,11 +37,12 @@ server.get("/dnslink/:name", async (req, res) => {
|
||||||
return failure(`Failed to fetch ${lookup} TXT record: ${error.message}`);
|
return failure(`Failed to fetch ${lookup} TXT record: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (records.length === 0) {
|
if (addresses.length === 0) {
|
||||||
return failure(`No TXT record found for ${lookup}`);
|
return failure(`No TXT record found for ${lookup}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const dnslinks = records.flat().filter((record) => dnslinkRegExp.test(record));
|
const records = addresses.flat();
|
||||||
|
const dnslinks = records.filter((record) => dnslinkRegExp.test(record));
|
||||||
|
|
||||||
if (dnslinks.length === 0) {
|
if (dnslinks.length === 0) {
|
||||||
return failure(`TXT records for ${lookup} found but none of them contained valid skynet dnslink - ${hint}`);
|
return failure(`TXT records for ${lookup} found but none of them contained valid skynet dnslink - ${hint}`);
|
||||||
|
@ -58,9 +61,25 @@ server.get("/dnslink/:name", async (req, res) => {
|
||||||
|
|
||||||
const skylink = matchSkylink[1];
|
const skylink = matchSkylink[1];
|
||||||
|
|
||||||
|
// check if _dnslink records contain skynet-sponsor-key entries
|
||||||
|
const sponsors = records.filter((record) => sponsorRegExp.test(record));
|
||||||
|
|
||||||
|
if (sponsors.length > 1) {
|
||||||
|
return failure(`Multiple TXT records with valid sponsor key found for ${lookup}, only one allowed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sponsors.length === 1) {
|
||||||
|
// extract just the key part from the record
|
||||||
|
const sponsor = sponsors[0].substring(sponsors[0].indexOf("=") + 1);
|
||||||
|
|
||||||
|
console.log(`${req.params.name} => ${skylink} | sponsor: ${sponsor}`);
|
||||||
|
|
||||||
|
return success({ skylink, sponsor });
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`${req.params.name} => ${skylink}`);
|
console.log(`${req.params.name} => ${skylink}`);
|
||||||
|
|
||||||
return success(skylink);
|
return success({ skylink });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,14 +8,14 @@
|
||||||
"express": "^4.17.3",
|
"express": "^4.17.3",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"graceful-fs": "^4.2.9",
|
"graceful-fs": "^4.2.10",
|
||||||
"hasha": "^5.2.2",
|
"hasha": "^5.2.2",
|
||||||
"http-status-codes": "^2.2.0",
|
"http-status-codes": "^2.2.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lowdb": "^1.0.0",
|
"lowdb": "^1.0.0",
|
||||||
"skynet-js": "^4.0.19-beta",
|
"skynet-js": "^4.0.19-beta",
|
||||||
"write-file-atomic": "^4.0.1",
|
"write-file-atomic": "^4.0.1",
|
||||||
"yargs": "^17.4.0"
|
"yargs": "^17.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^27.5.1",
|
"jest": "^27.5.1",
|
||||||
|
|
|
@ -1516,10 +1516,10 @@ got@^11.8.2:
|
||||||
p-cancelable "^2.0.0"
|
p-cancelable "^2.0.0"
|
||||||
responselike "^2.0.0"
|
responselike "^2.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.9:
|
graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.10, graceful-fs@^4.2.9:
|
||||||
version "4.2.9"
|
version "4.2.10"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||||
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
|
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||||
|
|
||||||
has-flag@^3.0.0:
|
has-flag@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
|
@ -3361,10 +3361,10 @@ yargs@^16.2.0:
|
||||||
y18n "^5.0.5"
|
y18n "^5.0.5"
|
||||||
yargs-parser "^20.2.2"
|
yargs-parser "^20.2.2"
|
||||||
|
|
||||||
yargs@^17.4.0:
|
yargs@^17.4.1:
|
||||||
version "17.4.0"
|
version "17.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.0.tgz#9fc9efc96bd3aa2c1240446af28499f0e7593d00"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284"
|
||||||
integrity sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==
|
integrity sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui "^7.0.2"
|
cliui "^7.0.2"
|
||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"copy-text-to-clipboard": "3.0.1",
|
"copy-text-to-clipboard": "3.0.1",
|
||||||
"crypto-browserify": "3.12.0",
|
"crypto-browserify": "3.12.0",
|
||||||
"framer-motion": "6.2.8",
|
"framer-motion": "6.2.8",
|
||||||
"gatsby": "4.11.1",
|
"gatsby": "4.11.2",
|
||||||
"gatsby-background-image": "1.6.0",
|
"gatsby-background-image": "1.6.0",
|
||||||
"gatsby-plugin-image": "2.11.1",
|
"gatsby-plugin-image": "2.11.1",
|
||||||
"gatsby-plugin-manifest": "4.11.1",
|
"gatsby-plugin-manifest": "4.11.1",
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
"gatsby-plugin-react-helmet": "5.10.0",
|
"gatsby-plugin-react-helmet": "5.10.0",
|
||||||
"gatsby-plugin-robots-txt": "1.7.0",
|
"gatsby-plugin-robots-txt": "1.7.0",
|
||||||
"gatsby-plugin-sharp": "4.10.2",
|
"gatsby-plugin-sharp": "4.10.2",
|
||||||
"gatsby-plugin-sitemap": "5.10.2",
|
"gatsby-plugin-sitemap": "5.11.1",
|
||||||
"gatsby-plugin-svgr": "3.0.0-beta.0",
|
"gatsby-plugin-svgr": "3.0.0-beta.0",
|
||||||
"gatsby-source-filesystem": "4.10.1",
|
"gatsby-source-filesystem": "4.10.1",
|
||||||
"gatsby-transformer-sharp": "4.10.0",
|
"gatsby-transformer-sharp": "4.10.0",
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
"autoprefixer": "10.4.4",
|
"autoprefixer": "10.4.4",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"cypress": "9.5.2",
|
"cypress": "9.5.2",
|
||||||
"prettier": "2.6.1",
|
"prettier": "2.6.2",
|
||||||
"tailwindcss": "3.0.23"
|
"tailwindcss": "3.0.23"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|
|
@ -4340,10 +4340,10 @@ create-ecdh@^4.0.0:
|
||||||
bn.js "^4.1.0"
|
bn.js "^4.1.0"
|
||||||
elliptic "^6.5.3"
|
elliptic "^6.5.3"
|
||||||
|
|
||||||
create-gatsby@^2.11.1:
|
create-gatsby@^2.11.2:
|
||||||
version "2.11.1"
|
version "2.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/create-gatsby/-/create-gatsby-2.11.1.tgz#8d73cce07ff0006386795ca1b74a0bdbb023500b"
|
resolved "https://registry.yarnpkg.com/create-gatsby/-/create-gatsby-2.11.2.tgz#b932bb16f024c929c4597225771275d54f3541bc"
|
||||||
integrity sha512-ltSLSsbQRoCXxKzgkxp5PBv60O1BL0IdeKKbgmwEcYxiDVw4pXPcFmIqMmvHfk9fqzbCyPzehIQHdlEpJGDYwQ==
|
integrity sha512-EHlULRVoiXoLM400sLYNtFRy5pemp2WoNKR6vjUlFnLBqn+BGe+TJAmKfwqHYFheXMozKqY2bW0ekuDj2x8zAg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.15.4"
|
"@babel/runtime" "^7.15.4"
|
||||||
|
|
||||||
|
@ -6158,10 +6158,10 @@ gatsby-background-image@1.6.0:
|
||||||
short-uuid "^4.2.0"
|
short-uuid "^4.2.0"
|
||||||
sort-media-queries "^0.2.2"
|
sort-media-queries "^0.2.2"
|
||||||
|
|
||||||
gatsby-cli@^4.11.1:
|
gatsby-cli@^4.11.2:
|
||||||
version "4.11.1"
|
version "4.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.11.1.tgz#a549a91cbd7e7bb9a98413cf604af09d10ef75c2"
|
resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.11.2.tgz#0bd5c218f378edb0e674f7ba7a903be202fe3620"
|
||||||
integrity sha512-RDOFIzKAyysa51x0mMoMtdVhyOX2UkBuEyelGqpuchl8b/ddka/cjEYHk3QRSq55+cBN0/1cTHt/A139ooAKUg==
|
integrity sha512-MypoVvMwWcDEtf5JTm1UTdGeOavRjnNRKfuUqvbhvb+q1vQ2xIFhu/pK9sdOlQfL6v6Fl8xwO2FuOfz+i53z3w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.14.0"
|
"@babel/code-frame" "^7.14.0"
|
||||||
"@babel/core" "^7.15.5"
|
"@babel/core" "^7.15.5"
|
||||||
|
@ -6179,7 +6179,7 @@ gatsby-cli@^4.11.1:
|
||||||
common-tags "^1.8.2"
|
common-tags "^1.8.2"
|
||||||
configstore "^5.0.1"
|
configstore "^5.0.1"
|
||||||
convert-hrtime "^3.0.0"
|
convert-hrtime "^3.0.0"
|
||||||
create-gatsby "^2.11.1"
|
create-gatsby "^2.11.2"
|
||||||
envinfo "^7.8.1"
|
envinfo "^7.8.1"
|
||||||
execa "^5.1.1"
|
execa "^5.1.1"
|
||||||
fs-exists-cached "^1.0.0"
|
fs-exists-cached "^1.0.0"
|
||||||
|
@ -6387,10 +6387,10 @@ gatsby-plugin-sharp@4.10.2:
|
||||||
svgo "1.3.2"
|
svgo "1.3.2"
|
||||||
uuid "3.4.0"
|
uuid "3.4.0"
|
||||||
|
|
||||||
gatsby-plugin-sitemap@5.10.2:
|
gatsby-plugin-sitemap@5.11.1:
|
||||||
version "5.10.2"
|
version "5.11.1"
|
||||||
resolved "https://registry.yarnpkg.com/gatsby-plugin-sitemap/-/gatsby-plugin-sitemap-5.10.2.tgz#208149b900b166c42aa88a5f5436f5c6bf6561e9"
|
resolved "https://registry.yarnpkg.com/gatsby-plugin-sitemap/-/gatsby-plugin-sitemap-5.11.1.tgz#863397fe9dd5aab89bda8db09ef9b877c960150e"
|
||||||
integrity sha512-X6pVbytl/QfdfGrnXAEKPf5vc38WIbclmHYIfbgjXUYA9yckTxnfuYZqkS2YwCmbcUTHG1ugcmXMeBGVo77IBQ==
|
integrity sha512-tt92KLUDS+eCrqSA5oYieDGjXLyUDXfYKEwLhYKXk7KlMMjporFJWVrc4Ba8WD04bUWVnzc2rqr19/zQI0ZIpQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.15.4"
|
"@babel/runtime" "^7.15.4"
|
||||||
common-tags "^1.8.2"
|
common-tags "^1.8.2"
|
||||||
|
@ -6514,10 +6514,10 @@ gatsby-worker@^1.11.0:
|
||||||
"@babel/core" "^7.15.5"
|
"@babel/core" "^7.15.5"
|
||||||
"@babel/runtime" "^7.15.4"
|
"@babel/runtime" "^7.15.4"
|
||||||
|
|
||||||
gatsby@4.11.1:
|
gatsby@4.11.2:
|
||||||
version "4.11.1"
|
version "4.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.11.1.tgz#ffe7754c9c368fd746bdeca808572641a378addb"
|
resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.11.2.tgz#fba8843316992e2f0eafc5ae3ee6fb37ce7aa953"
|
||||||
integrity sha512-ffEXb/mvZtB0cQ8javEkhruubxjTbZSsN81IYGGY/ym4YB+Zm1a8K0NV7DsRGsPO9nx7Z/D/OBVxVmse1Nnxzw==
|
integrity sha512-kJ2gHQzO3efV1BaynGfxi0divFOorUqxKom/QdIEnaj9VMkNRnrpNxUNsT4ljq+d9BTlq/0AAIpGNQZKC1ZgFg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/code-frame" "^7.14.0"
|
"@babel/code-frame" "^7.14.0"
|
||||||
"@babel/core" "^7.15.5"
|
"@babel/core" "^7.15.5"
|
||||||
|
@ -6587,7 +6587,7 @@ gatsby@4.11.1:
|
||||||
find-cache-dir "^3.3.2"
|
find-cache-dir "^3.3.2"
|
||||||
fs-exists-cached "1.0.0"
|
fs-exists-cached "1.0.0"
|
||||||
fs-extra "^10.0.0"
|
fs-extra "^10.0.0"
|
||||||
gatsby-cli "^4.11.1"
|
gatsby-cli "^4.11.2"
|
||||||
gatsby-core-utils "^3.11.1"
|
gatsby-core-utils "^3.11.1"
|
||||||
gatsby-graphiql-explorer "^2.11.0"
|
gatsby-graphiql-explorer "^2.11.0"
|
||||||
gatsby-legacy-polyfills "^2.11.0"
|
gatsby-legacy-polyfills "^2.11.0"
|
||||||
|
@ -8737,9 +8737,9 @@ mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.1:
|
||||||
minimist "^1.2.5"
|
minimist "^1.2.5"
|
||||||
|
|
||||||
moment@^2.29.1:
|
moment@^2.29.1:
|
||||||
version "2.29.1"
|
version "2.29.2"
|
||||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
|
||||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -9911,10 +9911,10 @@ prepend-http@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||||
|
|
||||||
prettier@2.6.1:
|
prettier@2.6.2:
|
||||||
version "2.6.1"
|
version "2.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
|
||||||
integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
|
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
|
||||||
|
|
||||||
pretty-bytes@^5.4.1, pretty-bytes@^5.6.0:
|
pretty-bytes@^5.4.1, pretty-bytes@^5.6.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
|
|
|
@ -2,16 +2,22 @@
|
||||||
|
|
||||||
set -e # exit on first error
|
set -e # exit on first error
|
||||||
|
|
||||||
while getopts d:t: flag
|
while getopts d:t:r: flag
|
||||||
do
|
do
|
||||||
case "${flag}" in
|
case "${flag}" in
|
||||||
d) delay=${OPTARG};;
|
d) delay=${OPTARG};;
|
||||||
t) timeout=${OPTARG};;
|
t) timeout=${OPTARG};;
|
||||||
|
r) reason=${OPTARG};;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
delay=${delay:-0} # default to no delay
|
delay=${delay:-0} # default to no delay
|
||||||
timeout=${timeout:-300} # default timeout is 300s
|
timeout=${timeout:-300} # default timeout is 300s
|
||||||
|
|
||||||
|
if [[ -z $reason ]]; then
|
||||||
|
echo "Please provide a reason for disabling the portal (use '-r <reason>')."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
countdown() {
|
countdown() {
|
||||||
local secs=$1
|
local secs=$1
|
||||||
while [ $secs -gt 0 ]; do
|
while [ $secs -gt 0 ]; do
|
||||||
|
@ -24,8 +30,8 @@ countdown() {
|
||||||
# delay disabling the portal
|
# delay disabling the portal
|
||||||
countdown $delay
|
countdown $delay
|
||||||
|
|
||||||
# stop healh-check so the server is taken our of load balancer
|
# stop health-check so the server is taken our of load balancer
|
||||||
docker exec health-check cli/disable
|
docker exec health-check cli disable $reason
|
||||||
|
|
||||||
# then wait 5 minutes for the load balancer to propagate the dns records
|
# then wait 5 minutes for the load balancer to propagate the dns records
|
||||||
countdown $timeout
|
countdown $timeout
|
||||||
|
|
|
@ -3,4 +3,4 @@
|
||||||
set -e # exit on first error
|
set -e # exit on first error
|
||||||
|
|
||||||
# start the health-checks service
|
# start the health-checks service
|
||||||
docker exec health-check cli/enable
|
docker exec health-check cli enable
|
||||||
|
|
Reference in New Issue