Merge branch 'master' into portal-latest

This commit is contained in:
Matthew Sevey 2022-04-13 07:12:18 -04:00
commit 752f4bbeb4
No known key found for this signature in database
GPG Key ID: 9ADDD344F13057F6
35 changed files with 358 additions and 358 deletions

View File

@ -5,8 +5,8 @@
Latest Skynet Webportal setup documentation and the setup process Skynet Labs
supports is located at https://docs.siasky.net/webportal-management/overview.
Some of the scripts and setup documentation contained in this repository
(`skynet-webportal`) can be outdated and generally should not be used.
Some scripts and setup documentation contained in this repository
(`skynet-webportal`) may be outdated and generally should not be used.
## Web application
@ -35,7 +35,7 @@ For the purposes of complying with our code license, you can use the following S
`fb6c9320bc7e01fbb9cd8d8c3caaa371386928793c736837832e634aaaa484650a3177d6714a`
## Running a Portal
For those interested in running a Webportal, head over to our developer docs [here](https://docs.siasky.net/webportal-management/overview.) to learn more.
For those interested in running a Webportal, head over to our developer docs [here](https://portal-docs.skynetlabs.com/) to learn more.
## Contributing

View File

@ -5,6 +5,7 @@ location / {
set $path $uri;
rewrite_by_lua_block {
local cjson = require("cjson")
local cache = ngx.shared.dnslink
local cache_value = cache:get(ngx.var.host)
@ -28,13 +29,23 @@ location / {
ngx.exit(ngx.status)
end
else
ngx.var.skylink = res.body
local resolved = cjson.decode(res.body)
ngx.var.skylink = resolved.skylink
if resolved.sponsor then
ngx.req.set_header("Skynet-Api-Key", resolved.sponsor)
end
local cache_ttl = 300 -- 5 minutes cache expire time
cache:set(ngx.var.host, ngx.var.skylink, cache_ttl)
cache:set(ngx.var.host, res.body, cache_ttl)
end
else
ngx.var.skylink = cache_value
local resolved = cjson.decode(cache_value)
ngx.var.skylink = resolved.skylink
if resolved.sponsor then
ngx.req.set_header("Skynet-Api-Key", resolved.sponsor)
end
end
ngx.var.skylink = require("skynet.skylink").parse(ngx.var.skylink)

View File

@ -1,4 +1,5 @@
import * as React from "react";
import { SWRConfig } from "swr";
import "@fontsource/sora/300.css"; // light
import "@fontsource/sora/400.css"; // normal
import "@fontsource/sora/500.css"; // medium
@ -6,6 +7,7 @@ import "@fontsource/sora/600.css"; // semibold
import "@fontsource/source-sans-pro/400.css"; // normal
import "@fontsource/source-sans-pro/600.css"; // semibold
import "./src/styles/global.css";
import swrConfig from "./src/lib/swrConfig";
import { MODAL_ROOT_ID } from "./src/components/Modal";
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
@ -13,10 +15,12 @@ export function wrapPageElement({ element, props }) {
const Layout = element.type.Layout ?? React.Fragment;
return (
<PortalSettingsProvider>
<SWRConfig value={swrConfig}>
<Layout {...props}>
{element}
<div id={MODAL_ROOT_ID} />
</Layout>
</SWRConfig>
</PortalSettingsProvider>
);
}

View File

@ -1,4 +1,5 @@
import * as React from "react";
import { SWRConfig } from "swr";
import "@fontsource/sora/300.css"; // light
import "@fontsource/sora/400.css"; // normal
import "@fontsource/sora/500.css"; // medium
@ -6,6 +7,7 @@ import "@fontsource/sora/600.css"; // semibold
import "@fontsource/source-sans-pro/400.css"; // normal
import "@fontsource/source-sans-pro/600.css"; // semibold
import "./src/styles/global.css";
import swrConfig from "./src/lib/swrConfig";
import { MODAL_ROOT_ID } from "./src/components/Modal";
import { PortalSettingsProvider } from "./src/contexts/portal-settings";
@ -13,10 +15,12 @@ export function wrapPageElement({ element, props }) {
const Layout = element.type.Layout ?? React.Fragment;
return (
<PortalSettingsProvider>
<SWRConfig value={swrConfig}>
<Layout {...props}>
{element}
<div id={MODAL_ROOT_ID} />
</Layout>
</SWRConfig>
</PortalSettingsProvider>
);
}

View File

@ -12,11 +12,13 @@ const BarTip = styled.span.attrs({
})``;
const BarLabel = styled.span.attrs({
className: "bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow",
className: "usage-label bg-white rounded border-2 border-palette-200 px-3 whitespace-nowrap absolute shadow",
})`
right: max(0%, ${({ $percentage }) => 100 - $percentage}%);
${({ $percentage }) => `
left: max(0%, ${$percentage}%);
top: -0.5rem;
transform: translateX(50%);
transform: translateX(-${$percentage}%);
`}
`;
export const GraphBar = ({ value, limit, label }) => {

View File

@ -7,13 +7,10 @@ import { ChevronDownIcon } from "../Icons";
const dropDown = keyframes`
0% {
transform: scaleY(0);
}
80% {
transform: scaleY(1.1);
transform: rotateX(-90deg);
}
100% {
transform: scaleY(1);
transform: rotateX(0deg);
}
`;
@ -35,10 +32,11 @@ const Flyout = styled.div.attrs(({ open }) => ({
bg-white shadow-md shadow-palette-200/50
${open ? "visible" : "invisible"}`,
}))`
transform-origin: top center;
animation: ${({ open }) =>
open
? css`
${dropDown} 0.1s ease-in-out
${dropDown} .15s ease-in-out forwards;
`
: "none"};
`;

View File

@ -4,6 +4,7 @@ import useSWR from "swr";
import { Table, TableBody, TableCell, TableRow } from "../Table";
import { ContainerLoadingIndicator } from "../LoadingIndicator";
import { ViewAllLink } from "./ViewAllLink";
import useFormattedActivityData from "./useFormattedActivityData";
export default function ActivityTable({ type }) {
@ -22,6 +23,7 @@ export default function ActivityTable({ type }) {
}
return (
<>
<Table style={{ tableLayout: "fixed" }}>
<TableBody>
{items.map(({ id, name, type, size, date, skylink }) => (
@ -37,5 +39,7 @@ export default function ActivityTable({ type }) {
))}
</TableBody>
</Table>
<ViewAllLink to={`/files?tab=${type}`} />
</>
);
}

View File

@ -1,21 +1,10 @@
import * as React from "react";
import { Link } from "gatsby";
import { Panel } from "../Panel";
import { Tab, TabPanel, Tabs } from "../Tabs";
import { ArrowRightIcon } from "../Icons";
import ActivityTable from "./ActivityTable";
const ViewAllLink = (props) => (
<Link className="inline-flex mt-6 items-center gap-3 ease-in-out hover:brightness-90" {...props}>
<span className="bg-primary rounded-full w-[32px] h-[32px] inline-flex justify-center items-center">
<ArrowRightIcon />
</span>
<span className="font-sans text-xs uppercase text-palette-400">View all</span>
</Link>
);
export default function LatestActivity() {
return (
<Panel title="Latest activity">
@ -24,11 +13,9 @@ export default function LatestActivity() {
<Tab id="downloads" title="Downloads" />
<TabPanel tabId="uploads" className="pt-4">
<ActivityTable type="uploads" />
<ViewAllLink to="/files?tab=uploads" />
</TabPanel>
<TabPanel tabId="downloads" className="pt-4">
<ActivityTable type="downloads" />
<ViewAllLink to="/files?tab=downloads" />
</TabPanel>
</Tabs>
</Panel>

View File

@ -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>
);

View File

@ -97,7 +97,7 @@ export const NavBar = () => {
as="button"
onClick={onLogout}
activeClassName="text-primary"
className="cursor-pointer"
className="cursor-pointer w-full"
icon={LockClosedIcon}
label="Log out"
/>

View File

@ -109,7 +109,6 @@ export default function UploaderItem({ onUploadStateChange, upload }) {
{upload.status === "uploading" && (
<span className="uppercase tabular-nums">{Math.floor(upload.progress * 100)}%</span>
)}
{upload.status === "processing" && <span className="uppercase text-palette-300">Wait</span>}
{upload.status === "complete" && (
<button
className="uppercase hover:text-primary transition-colors duration-200"

View File

@ -82,7 +82,7 @@ export const LoginForm = ({ onSuccess }) => {
</div>
<p className="text-sm text-center mt-8">
Don't have an account? <HighlightedLink to="/auth/signup">Sign up</HighlightedLink>
Don't have an account? <HighlightedLink to="/auth/registration">Sign up</HighlightedLink>
</p>
</Form>
)}

View File

@ -16,7 +16,7 @@ const fetcher = async (path) => {
};
export const PortalSettingsProvider = ({ children }) => {
const { data, error } = useSWRImmutable("/__internal/do/not/use/accounts", fetcher);
const { data, error } = useSWRImmutable("__internal/do/not/use/accounts", fetcher);
const [loading, setLoading] = useState(true);
const [settings, setSettings] = useState(defaultSettings);

View File

@ -1,17 +1,41 @@
import { navigate } from "gatsby";
import { useEffect, useState } from "react";
import useSWRImmutable from "swr/immutable";
import { UnauthorizedError } from "../../lib/swrConfig";
import { FullScreenLoadingIndicator } from "../../components/LoadingIndicator";
import { UserContext } from "./UserContext";
export const UserProvider = ({ children }) => {
export const UserProvider = ({ children, allowGuests = false, allowAuthenticated = true }) => {
const { data: user, error, mutate } = useSWRImmutable("user");
const [loading, setLoading] = useState(true);
useEffect(() => {
if (user || error) {
const guard = async () => {
if (user) {
if (!allowAuthenticated) {
navigate("/");
} else {
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>
);
};

View File

@ -1,9 +1,7 @@
import * as React from "react";
import styled from "styled-components";
import { SWRConfig } from "swr";
import { UserProvider } from "../contexts/user";
import { guestsOnly, allUsers } from "../lib/swrConfig";
const Layout = styled.div.attrs({
className: "min-h-screen w-screen bg-black flex",
@ -22,12 +20,11 @@ const Content = styled.div.attrs({
})``;
const AuthLayout =
(swrConfig) =>
({ children }) => {
return (
(userProviderProps) =>
({ children }) =>
(
<>
<SWRConfig value={swrConfig}>
<UserProvider>
<UserProvider {...userProviderProps}>
<Layout>
<SloganContainer className="pl-20 pr-20 lg:pr-30 xl:pr-40">
<div className="">
@ -36,15 +33,26 @@ const AuthLayout =
</h1>
</div>
</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>
</UserProvider>
</SWRConfig>
</>
);
};
// 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,
});

View File

@ -1,8 +1,5 @@
import * as React from "react";
import styled from "styled-components";
import { SWRConfig } from "swr";
import { authenticatedOnly } from "../lib/swrConfig";
import { PageContainer } from "../components/PageContainer";
import { NavBar } from "../components/NavBar";
@ -30,22 +27,16 @@ const Layout = ({ children }) => {
);
};
const DashboardLayout = ({ children }) => {
return (
const DashboardLayout = ({ children }) => (
<>
<SWRConfig value={authenticatedOnly}>
<UserProvider>
<Layout>
<NavBar />
<PageContainer>
<main className="mt-14">{children}</main>
</PageContainer>
<PageContainer className="mt-2 md:mt-14">{children}</PageContainer>
<Footer />
</Layout>
</UserProvider>
</SWRConfig>
</>
);
};
);
export default DashboardLayout;

View File

@ -1,43 +1,12 @@
import * as React from "react";
import { Link } from "gatsby";
import styled from "styled-components";
import { SWRConfig } from "swr";
import { authenticatedOnly } from "../lib/swrConfig";
import { PageContainer } from "../components/PageContainer";
import { NavBar } from "../components/NavBar";
import { Footer } from "../components/Footer";
import { UserProvider, useUser } from "../contexts/user";
import { ContainerLoadingIndicator } from "../components/LoadingIndicator";
const Wrapper = styled.div.attrs({
className: "min-h-screen overflow-hidden",
})`
background-image: url(/images/dashboard-bg.svg);
background-position: center -280px;
background-repeat: no-repeat;
`;
const Layout = ({ children }) => {
const { user } = useUser();
// Prevent from flashing the dashboard screen to unauthenticated users.
return (
<Wrapper>
{!user && (
<div className="fixed inset-0 flex justify-center items-center bg-palette-100/50">
<ContainerLoadingIndicator className="!text-palette-200/50" />
</div>
)}
{user && <>{children}</>}
</Wrapper>
);
};
import DashboardLayout from "./DashboardLayout";
const Sidebar = () => (
<aside className="w-full lg:w-48 bg-white text-sm font-sans font-light text-palette-600 shrink-0">
<nav>
<aside className="w-full lg:w-48 text-sm font-sans font-light text-palette-600 shrink-0">
<nav className="bg-white">
<SidebarLink activeClassName="!border-l-primary" to="/settings">
Account
</SidebarLink>
@ -55,7 +24,7 @@ const Sidebar = () => (
);
const SidebarLink = styled(Link).attrs({
className: `h-12 py-3 px-6 h-full w-full flex
className: `h-12 py-3 px-6 w-full flex
border-l-2 border-l-palette-200
border-b border-b-palette-100 last:border-b-transparent`,
})``;
@ -67,21 +36,13 @@ const Content = styled.main.attrs({
`;
const UserSettingsLayout = ({ children }) => (
<SWRConfig value={authenticatedOnly}>
<UserProvider>
<Layout>
<NavBar />
<PageContainer className="mt-2 md:mt-14">
<DashboardLayout>
<h6 className="hidden md:block mb-2 text-palette-400">Settings</h6>
<div className="flex flex-col lg:flex-row">
<Sidebar />
<Content className="lg:w-settings-lg xl:w-settings-xl">{children}</Content>
</div>
</PageContainer>
<Footer />
</Layout>
</UserProvider>
</SWRConfig>
</DashboardLayout>
);
export default UserSettingsLayout;

View File

@ -1,39 +1,22 @@
import { navigate } from "gatsby";
import { StatusCodes } from "http-status-codes";
// TODO: portal-aware URL
const baseUrl = process.env.NODE_ENV !== "production" ? "/api" : "https://account.skynetpro.net/api";
export class UnauthorizedError extends Error {}
const redirectUnauthenticated = (key) =>
fetch(`${baseUrl}/${key}`).then((response) => {
if (response.status === StatusCodes.UNAUTHORIZED) {
navigate(`/auth/login?return_to=${encodeURIComponent(window.location.href)}`);
return null;
}
return response.json();
});
const redirectAuthenticated = (key) =>
fetch(`${baseUrl}/${key}`).then(async (response) => {
const config = {
fetcher: (key) =>
fetch(`/api/${key}`).then(async (response) => {
if (response.ok) {
await navigate("/");
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();
throw new Error(data?.message || `Error occured when trying to fetch: ${key}`);
});
export const allUsers = {
fetcher: (key) => fetch(`${baseUrl}/${key}`).then((response) => response.json()),
if (response.status === StatusCodes.UNAUTHORIZED) {
throw new UnauthorizedError(data?.message || "Unauthorized");
}
throw new Error(data?.message || `Error occurred when trying to fetch: ${key}`);
}),
};
export const authenticatedOnly = {
fetcher: redirectUnauthenticated,
};
export const guestsOnly = {
fetcher: redirectAuthenticated,
};
export default config;

View File

@ -22,16 +22,11 @@ const LoginPage = ({ location }) => {
<Metadata>
<title>Sign In</title>
</Metadata>
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
<div className="mb-4 md:mb-16">
<img src="/images/logo-black-text.svg" alt="Skynet" />
</div>
<LoginForm
onSuccess={async () => {
await refreshUserState();
}}
/>
</div>
</>
);
};

View File

@ -61,11 +61,7 @@ const SignUpPage = () => {
<Metadata>
<title>Sign Up</title>
</Metadata>
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
<div className="mb-4 md:mb-16">
<img src="/images/logo-black-text.svg" alt="Skynet" />
</div>
<div className="flex flex-col">
{!settings.areAccountsEnabled && <Alert $variant="info">Accounts are not enabled on this portal.</Alert>}
{settings.areAccountsEnabled && (
@ -73,11 +69,17 @@ const SignUpPage = () => {
{settings.isSubscriptionRequired ? <PaidPortalHeader /> : <FreePortalHeader />}
{state !== State.Success && (
<>
<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 && (
<div className="text-center">
<div>
<p className="text-primary font-semibold">Please check your inbox and confirm your email address.</p>
<p>You will be redirected to your dashboard shortly.</p>
<HighlightedLink to="/">Click here to go there now.</HighlightedLink>
@ -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>
</PlansProvider>
);

View File

@ -20,10 +20,6 @@ const ResetPasswordPage = () => {
<Metadata>
<title>Reset Password</title>
</Metadata>
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
<div className="mb-4 md:mb-16">
<img src="/images/logo-black-text.svg" alt="Skynet" />
</div>
{state !== State.Success && (
<RecoveryForm onSuccess={() => setState(State.Success)} onFailure={() => setState(State.Failure)} />
)}
@ -41,10 +37,9 @@ const ResetPasswordPage = () => {
Suddenly remembered your password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
</p>
<p>
Don't actually have an account? <HighlightedLink to="/auth/signup">Create one!</HighlightedLink>
Don't actually have an account? <HighlightedLink to="/auth/registration">Create one!</HighlightedLink>
</p>
</div>
</div>
</>
);
};

View File

@ -57,10 +57,6 @@ const EmailConfirmationPage = ({ location }) => {
<Metadata>
<title>Confirm E-mail Address</title>
</Metadata>
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
<div className="mb-4 md:mb-16">
<img src="/images/logo-black-text.svg" alt="Skynet" />
</div>
<div className="text-center">
{state === State.Pure && <p>Please wait while we verify your account...</p>}
@ -74,7 +70,6 @@ const EmailConfirmationPage = ({ location }) => {
{state === State.Failure && <p className="text-error">Something went wrong, please try again later.</p>}
</div>
</div>
</>
);
};

View File

@ -24,10 +24,6 @@ const RecoverPage = ({ location }) => {
<Metadata>
<title>Recover Your Account</title>
</Metadata>
<div className="bg-white px-8 py-10 md:py-32 lg:px-16 xl:px-28 min-h-screen">
<div className="mb-4 md:mb-16">
<img src="/images/logo-black-text.svg" alt="Skynet" />
</div>
{state !== State.Success && (
<ResetPasswordForm
token={token}
@ -52,7 +48,6 @@ const RecoverPage = ({ location }) => {
<p className="text-sm text-center mt-8">
Suddenly remembered your old password? <HighlightedLink to="/auth/login">Sign in</HighlightedLink>
</p>
</div>
</>
);
};

View File

@ -11242,9 +11242,9 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
version "2.29.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
move-concurrently@^1.0.1:
version "1.0.1"

View File

@ -10,8 +10,8 @@
"dependencies": {
"@fontsource/sora": "4.5.5",
"@fontsource/source-sans-pro": "4.5.6",
"@stripe/react-stripe-js": "1.7.0",
"@stripe/stripe-js": "1.26.0",
"@stripe/react-stripe-js": "1.7.1",
"@stripe/stripe-js": "1.27.0",
"classnames": "2.3.1",
"copy-text-to-clipboard": "^3.0.1",
"dayjs": "1.11.0",
@ -27,7 +27,7 @@
"react-dom": "17.0.2",
"react-toastify": "8.2.0",
"skynet-js": "3.0.2",
"stripe": "8.215.0",
"stripe": "8.216.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.4",
"eslint": "8.12.0",
"eslint": "8.13.0",
"eslint-config-next": "12.1.4",
"postcss": "8.4.12",
"prettier": "2.6.2",

View File

@ -3,7 +3,14 @@ import { useFormik, getIn, setIn } from "formik";
import classnames from "classnames";
import SelfServiceMessages from "./SelfServiceMessages";
export default function SelfServiceForm({ fieldsConfig, onSubmit, title, validationSchema = null, button = "Submit" }) {
export default function SelfServiceForm({
fieldsConfig,
onSubmit,
title,
onError,
validationSchema = null,
button = "Submit",
}) {
const [messages, setMessages] = React.useState([]);
const fields = fieldsConfig.sort((a, b) => (a.position < b.position ? -1 : 1));
const formik = useFormik({
@ -21,6 +28,9 @@ export default function SelfServiceForm({ fieldsConfig, onSubmit, title, validat
const data = await error.response.json();
if (data.message) {
if (typeof onError === "function") {
onError(data.message);
}
setMessages((messages) => [...messages, { type: "error", text: data.message }]);
}
} else {

View File

@ -23,6 +23,8 @@ export default function Recovery() {
useAnonRoute(); // ensure user is not logged in
const [success, setSuccess] = React.useState(false);
const [skynetFreeInviteVisible, setSkynetFreeInviteVisible] = React.useState(false);
const isSiaskyNet = typeof window !== "undefined" && window.location.hostname === "account.siasky.net";
const onSubmit = async (values) => {
await accountsApi.post("user/recover/request", {
@ -64,6 +66,16 @@ export default function Recovery() {
</Link>{" "}
for a new account
</p>
{skynetFreeInviteVisible && isSiaskyNet && (
<div className="font-content rounded border border-blue-200 mt-6 p-4 bg-blue-100">
<p>
All Siasky.net accounts have been moved to{" "}
<a className="text-primary" href="https://skynetfree.net">
SkynetFree.net
</a>
</p>
</div>
)}
</div>
{!success && (
@ -72,6 +84,9 @@ export default function Recovery() {
validationSchema={validationSchema}
onSubmit={onSubmit}
button="Send recovery link"
onError={(errorMessage) =>
setSkynetFreeInviteVisible(errorMessage === "registrations are currently disabled")
}
/>
)}

View File

@ -175,17 +175,17 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64"
integrity sha512-ZK5v4bJwgXldAUA8r3q9YKfCwOqoHTK/ZqRjSeRXQrBXWouoPnS4MQtgC4AXGiiBuUu5wxrRgTlv0ktmM4P1Aw==
"@stripe/react-stripe-js@1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.0.tgz#83c993a09a903703205d556617f9729784a896c3"
integrity sha512-L20v8Jq0TDZFL2+y+uXD751t6q9SalSFkSYZpmZ2VWrGZGK7HAGfRQ804dzYSSr5fGenW6iz6y7U0YKfC/TK3g==
"@stripe/react-stripe-js@1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.1.tgz#6e1db8f4a0eaf2193b153173d4aa7c38b681310d"
integrity sha512-GiUPoMo0xVvmpRD6JR9JAhAZ0W3ZpnYZNi0KE+91+tzrSFVpChKZbeSsJ5InlZhHFk9NckJCt1wOYBTqNsvt3A==
dependencies:
prop-types "^15.7.2"
"@stripe/stripe-js@1.26.0":
version "1.26.0"
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.26.0.tgz#45670924753c01e18d0544ea1f1067b474aaa96f"
integrity sha512-4R1vC75yKaCVFARW3bhelf9+dKt4NP4iZY/sIjGK7AAMBVvZ47eG74NvsAIUdUnhOXSWFMjdFWqv+etk5BDW4g==
"@stripe/stripe-js@1.27.0":
version "1.27.0"
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.27.0.tgz#ab0c82fa89fd40260de4414f69868b769e810550"
integrity sha512-SEiybUBu+tlsFKuzdFFydxxjkbrdzHo0tz/naYC5Dt9or/Ux2gcKJBPYQ4RmqQCNHFxgyNj6UYsclywwhe2inQ==
"@tailwindcss/forms@0.5.0":
version "0.5.0"
@ -923,10 +923,10 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
eslint@8.12.0:
version "8.12.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.12.0.tgz#c7a5bd1cfa09079aae64c9076c07eada66a46e8e"
integrity sha512-it1oBL9alZg1S8UycLm5YDMAkIhtH6FtAzuZs6YvoGVldWjbS08BkAdb/ymP9LlAyq8koANu32U7Ib/w+UNh8Q==
eslint@8.13.0:
version "8.13.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.13.0.tgz#6fcea43b6811e655410f5626cfcf328016badcd7"
integrity sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==
dependencies:
"@eslint/eslintrc" "^1.2.1"
"@humanwhocodes/config-array" "^0.9.2"
@ -1644,14 +1644,7 @@ mini-svg-data-uri@^1.2.3:
resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.3.3.tgz#91d2c09f45e056e5e1043340b8b37ba7b50f4fac"
integrity sha512-+fA2oRcR1dJI/7ITmeQJDrYWks0wodlOz0pAEhKYJ2IVc1z0AnwJUsKY2fzFmPAM3Jo9J0rBx8JAA9QQSJ5PuA==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
minimatch@^3.1.2:
minimatch@^3.0.4, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@ -1965,16 +1958,7 @@ pretty-bytes@6.0.0:
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.0.0.tgz#928be2ad1f51a2e336add8ba764739f9776a8140"
integrity sha512-6UqkYefdogmzqAZWzJ7laYeJnaXDy2/J+ZqiiMtS7t7OfpXWTlaeGMwX8U6EFvPV/YWWEKRkS8hKS4k60WHTOg==
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
dependencies:
loose-envify "^1.4.0"
object-assign "^4.1.1"
react-is "^16.8.1"
prop-types@^15.8.1:
prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -2036,7 +2020,7 @@ react-fast-compare@^2.0.1:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9"
integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==
react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@ -2264,10 +2248,10 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
stripe@8.215.0:
version "8.215.0"
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.215.0.tgz#bb464e256fb83da9ea2f514711fd0f6f7ae7dc9a"
integrity sha512-M+7iTZ9bzTkU1Ms+Zsuh0mTQfEzOjMoqyEaVBpuUmdbWTvshavzpAihsOkfabEu+sNY0vdbQxxHZ4kI3W8pKHQ==
stripe@8.216.0:
version "8.216.0"
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.216.0.tgz#23c047498526d13a238c3aca7b4dc8cbbd522e46"
integrity sha512-LY8cNGizEnklIa4T82l6mZW0HS4cfzo1hNuhT+ZR9PBkmYcSUbg3ilUBVF0FCd4RP+NA44VEVfoSTTZ1Gg5+rQ==
dependencies:
"@types/node" ">=8.1.0"
qs "^6.10.3"

View File

@ -8,12 +8,14 @@ const port = Number(process.env.DNSLINK_API_PORT) || 3100;
const server = express();
const dnslinkNamespace = "skynet-ns";
const sponsorNamespace = "skynet-sponsor-key";
const dnslinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/.+$`);
const sponsorRegExp = new RegExp(`^${sponsorNamespace}=[a-zA-Z0-9]+$`);
const dnslinkSkylinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/([a-zA-Z0-9_-]{46}|[a-z0-9]{55})`);
const hint = `valid example: dnslink=/${dnslinkNamespace}/3ACpC9Umme41zlWUgMQh1fw0sNwgWwyfDDhRQ9Sppz9hjQ`;
server.get("/dnslink/:name", async (req, res) => {
const success = (skylink) => res.send(skylink);
const success = (response) => res.json(response);
const failure = (message) => res.status(400).send(message);
if (!isValidDomain(req.params.name)) {
@ -22,7 +24,7 @@ server.get("/dnslink/:name", async (req, res) => {
const lookup = `_dnslink.${req.params.name}`;
dns.resolveTxt(lookup, (error, records) => {
dns.resolveTxt(lookup, (error, addresses) => {
if (error) {
if (error.code === "ENOTFOUND") {
return failure(`ENOTFOUND: ${lookup} TXT record doesn't exist`);
@ -35,11 +37,12 @@ server.get("/dnslink/:name", async (req, res) => {
return failure(`Failed to fetch ${lookup} TXT record: ${error.message}`);
}
if (records.length === 0) {
if (addresses.length === 0) {
return failure(`No TXT record found for ${lookup}`);
}
const dnslinks = records.flat().filter((record) => dnslinkRegExp.test(record));
const records = addresses.flat();
const dnslinks = records.filter((record) => dnslinkRegExp.test(record));
if (dnslinks.length === 0) {
return failure(`TXT records for ${lookup} found but none of them contained valid skynet dnslink - ${hint}`);
@ -58,9 +61,25 @@ server.get("/dnslink/:name", async (req, res) => {
const skylink = matchSkylink[1];
// check if _dnslink records contain skynet-sponsor-key entries
const sponsors = records.filter((record) => sponsorRegExp.test(record));
if (sponsors.length > 1) {
return failure(`Multiple TXT records with valid sponsor key found for ${lookup}, only one allowed`);
}
if (sponsors.length === 1) {
// extract just the key part from the record
const sponsor = sponsors[0].substring(sponsors[0].indexOf("=") + 1);
console.log(`${req.params.name} => ${skylink} | sponsor: ${sponsor}`);
return success({ skylink, sponsor });
}
console.log(`${req.params.name} => ${skylink}`);
return success(skylink);
return success({ skylink });
});
});

View File

@ -8,14 +8,14 @@
"express": "^4.17.3",
"form-data": "^4.0.0",
"got": "^11.8.2",
"graceful-fs": "^4.2.9",
"graceful-fs": "^4.2.10",
"hasha": "^5.2.2",
"http-status-codes": "^2.2.0",
"lodash": "^4.17.21",
"lowdb": "^1.0.0",
"skynet-js": "^4.0.19-beta",
"write-file-atomic": "^4.0.1",
"yargs": "^17.4.0"
"yargs": "^17.4.1"
},
"devDependencies": {
"jest": "^27.5.1",

View File

@ -1516,10 +1516,10 @@ got@^11.8.2:
p-cancelable "^2.0.0"
responselike "^2.0.0"
graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.9:
version "4.2.9"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.2.10, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
has-flag@^3.0.0:
version "3.0.0"
@ -3361,10 +3361,10 @@ yargs@^16.2.0:
y18n "^5.0.5"
yargs-parser "^20.2.2"
yargs@^17.4.0:
version "17.4.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.0.tgz#9fc9efc96bd3aa2c1240446af28499f0e7593d00"
integrity sha512-WJudfrk81yWFSOkZYpAZx4Nt7V4xp7S/uJkX0CnxovMCt1wCE8LNftPpNuF9X/u9gN5nsD7ycYtRcDf2pL3UiA==
yargs@^17.4.1:
version "17.4.1"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284"
integrity sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==
dependencies:
cliui "^7.0.2"
escalade "^3.1.1"

View File

@ -13,7 +13,7 @@
"copy-text-to-clipboard": "3.0.1",
"crypto-browserify": "3.12.0",
"framer-motion": "6.2.8",
"gatsby": "4.11.1",
"gatsby": "4.11.2",
"gatsby-background-image": "1.6.0",
"gatsby-plugin-image": "2.11.1",
"gatsby-plugin-manifest": "4.11.1",
@ -21,7 +21,7 @@
"gatsby-plugin-react-helmet": "5.10.0",
"gatsby-plugin-robots-txt": "1.7.0",
"gatsby-plugin-sharp": "4.10.2",
"gatsby-plugin-sitemap": "5.10.2",
"gatsby-plugin-sitemap": "5.11.1",
"gatsby-plugin-svgr": "3.0.0-beta.0",
"gatsby-source-filesystem": "4.10.1",
"gatsby-transformer-sharp": "4.10.0",
@ -49,7 +49,7 @@
"autoprefixer": "10.4.4",
"cross-env": "7.0.3",
"cypress": "9.5.2",
"prettier": "2.6.1",
"prettier": "2.6.2",
"tailwindcss": "3.0.23"
},
"keywords": [

View File

@ -4340,10 +4340,10 @@ create-ecdh@^4.0.0:
bn.js "^4.1.0"
elliptic "^6.5.3"
create-gatsby@^2.11.1:
version "2.11.1"
resolved "https://registry.yarnpkg.com/create-gatsby/-/create-gatsby-2.11.1.tgz#8d73cce07ff0006386795ca1b74a0bdbb023500b"
integrity sha512-ltSLSsbQRoCXxKzgkxp5PBv60O1BL0IdeKKbgmwEcYxiDVw4pXPcFmIqMmvHfk9fqzbCyPzehIQHdlEpJGDYwQ==
create-gatsby@^2.11.2:
version "2.11.2"
resolved "https://registry.yarnpkg.com/create-gatsby/-/create-gatsby-2.11.2.tgz#b932bb16f024c929c4597225771275d54f3541bc"
integrity sha512-EHlULRVoiXoLM400sLYNtFRy5pemp2WoNKR6vjUlFnLBqn+BGe+TJAmKfwqHYFheXMozKqY2bW0ekuDj2x8zAg==
dependencies:
"@babel/runtime" "^7.15.4"
@ -6158,10 +6158,10 @@ gatsby-background-image@1.6.0:
short-uuid "^4.2.0"
sort-media-queries "^0.2.2"
gatsby-cli@^4.11.1:
version "4.11.1"
resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.11.1.tgz#a549a91cbd7e7bb9a98413cf604af09d10ef75c2"
integrity sha512-RDOFIzKAyysa51x0mMoMtdVhyOX2UkBuEyelGqpuchl8b/ddka/cjEYHk3QRSq55+cBN0/1cTHt/A139ooAKUg==
gatsby-cli@^4.11.2:
version "4.11.2"
resolved "https://registry.yarnpkg.com/gatsby-cli/-/gatsby-cli-4.11.2.tgz#0bd5c218f378edb0e674f7ba7a903be202fe3620"
integrity sha512-MypoVvMwWcDEtf5JTm1UTdGeOavRjnNRKfuUqvbhvb+q1vQ2xIFhu/pK9sdOlQfL6v6Fl8xwO2FuOfz+i53z3w==
dependencies:
"@babel/code-frame" "^7.14.0"
"@babel/core" "^7.15.5"
@ -6179,7 +6179,7 @@ gatsby-cli@^4.11.1:
common-tags "^1.8.2"
configstore "^5.0.1"
convert-hrtime "^3.0.0"
create-gatsby "^2.11.1"
create-gatsby "^2.11.2"
envinfo "^7.8.1"
execa "^5.1.1"
fs-exists-cached "^1.0.0"
@ -6387,10 +6387,10 @@ gatsby-plugin-sharp@4.10.2:
svgo "1.3.2"
uuid "3.4.0"
gatsby-plugin-sitemap@5.10.2:
version "5.10.2"
resolved "https://registry.yarnpkg.com/gatsby-plugin-sitemap/-/gatsby-plugin-sitemap-5.10.2.tgz#208149b900b166c42aa88a5f5436f5c6bf6561e9"
integrity sha512-X6pVbytl/QfdfGrnXAEKPf5vc38WIbclmHYIfbgjXUYA9yckTxnfuYZqkS2YwCmbcUTHG1ugcmXMeBGVo77IBQ==
gatsby-plugin-sitemap@5.11.1:
version "5.11.1"
resolved "https://registry.yarnpkg.com/gatsby-plugin-sitemap/-/gatsby-plugin-sitemap-5.11.1.tgz#863397fe9dd5aab89bda8db09ef9b877c960150e"
integrity sha512-tt92KLUDS+eCrqSA5oYieDGjXLyUDXfYKEwLhYKXk7KlMMjporFJWVrc4Ba8WD04bUWVnzc2rqr19/zQI0ZIpQ==
dependencies:
"@babel/runtime" "^7.15.4"
common-tags "^1.8.2"
@ -6514,10 +6514,10 @@ gatsby-worker@^1.11.0:
"@babel/core" "^7.15.5"
"@babel/runtime" "^7.15.4"
gatsby@4.11.1:
version "4.11.1"
resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.11.1.tgz#ffe7754c9c368fd746bdeca808572641a378addb"
integrity sha512-ffEXb/mvZtB0cQ8javEkhruubxjTbZSsN81IYGGY/ym4YB+Zm1a8K0NV7DsRGsPO9nx7Z/D/OBVxVmse1Nnxzw==
gatsby@4.11.2:
version "4.11.2"
resolved "https://registry.yarnpkg.com/gatsby/-/gatsby-4.11.2.tgz#fba8843316992e2f0eafc5ae3ee6fb37ce7aa953"
integrity sha512-kJ2gHQzO3efV1BaynGfxi0divFOorUqxKom/QdIEnaj9VMkNRnrpNxUNsT4ljq+d9BTlq/0AAIpGNQZKC1ZgFg==
dependencies:
"@babel/code-frame" "^7.14.0"
"@babel/core" "^7.15.5"
@ -6587,7 +6587,7 @@ gatsby@4.11.1:
find-cache-dir "^3.3.2"
fs-exists-cached "1.0.0"
fs-extra "^10.0.0"
gatsby-cli "^4.11.1"
gatsby-cli "^4.11.2"
gatsby-core-utils "^3.11.1"
gatsby-graphiql-explorer "^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"
moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
version "2.29.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4"
integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==
ms@2.0.0:
version "2.0.0"
@ -9911,10 +9911,10 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
prettier@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.1.tgz#d472797e0d7461605c1609808e27b80c0f9cfe17"
integrity sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==
prettier@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032"
integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==
pretty-bytes@^5.4.1, pretty-bytes@^5.6.0:
version "5.6.0"

View File

@ -2,16 +2,22 @@
set -e # exit on first error
while getopts d:t: flag
while getopts d:t:r: flag
do
case "${flag}" in
d) delay=${OPTARG};;
t) timeout=${OPTARG};;
r) reason=${OPTARG};;
esac
done
delay=${delay:-0} # default to no delay
timeout=${timeout:-300} # default timeout is 300s
if [[ -z $reason ]]; then
echo "Please provide a reason for disabling the portal (use '-r <reason>')."
exit 1
fi
countdown() {
local secs=$1
while [ $secs -gt 0 ]; do
@ -24,8 +30,8 @@ countdown() {
# delay disabling the portal
countdown $delay
# stop healh-check so the server is taken our of load balancer
docker exec health-check cli/disable
# stop health-check so the server is taken our of load balancer
docker exec health-check cli disable $reason
# then wait 5 minutes for the load balancer to propagate the dns records
countdown $timeout

View File

@ -3,4 +3,4 @@
set -e # exit on first error
# start the health-checks service
docker exec health-check cli/enable
docker exec health-check cli enable