feat core react: Add account pages to supported Components

This commit is contained in:
prplwtf 2024-01-06 16:32:17 +01:00
parent 5d80d70974
commit 456145cba4
10 changed files with 308 additions and 0 deletions

View file

@ -634,10 +634,15 @@ if [[ ( $2 == "-i" ) || ( $2 == "-install" ) ]]; then VCMD="y"
# place component items
# -> PLACE_REACT "$Components_" "path/.tsx" "$OldComponents_"
# navigation
PLACE_REACT "$Components_Navigation_NavigationBar_BeforeNavigation" "Navigation/NavigationBar/BeforeNavigation.tsx" "$OldComponents_Navigation_NavigationBar_BeforeNavigation"
PLACE_REACT "$Components_Navigation_NavigationBar_AdditionalItems" "Navigation/NavigationBar/AdditionalItems.tsx" "$OldComponents_Navigation_NavigationBar_AdditionalItems"
PLACE_REACT "$Components_Navigation_NavigationBar_AfterNavigation" "Navigation/NavigationBar/AfterNavigation.tsx" "$OldComponents_Navigation_NavigationBar_AfterNavigation"
# server
PLACE_REACT "$Components_Server_Terminal_BeforeContent" "Server/Terminal/BeforeContent.tsx" "$OldComponents_Server_Terminal_BeforeContent"
PLACE_REACT "$Components_Server_Terminal_AfterContent" "Server/Terminal/AfterContent.tsx" "$OldComponents_Server_Terminal_AfterContent"
@ -670,6 +675,17 @@ if [[ ( $2 == "-i" ) || ( $2 == "-install" ) ]]; then VCMD="y"
PLACE_REACT "$Components_Server_Settings_BeforeContent" "Server/Settings/BeforeContent.tsx" "$OldComponents_Server_Settings_BeforeContent"
PLACE_REACT "$Components_Server_Settings_AfterContent" "Server/Settings/AfterContent.tsx" "$OldComponents_Server_Settings_AfterContent"
# account
PLACE_REACT "$Components_Account_Overview_BeforeContent" "Account/Overview/BeforeContent.tsx" "$OldComponents_Account_Overview_BeforeContent"
PLACE_REACT "$Components_Account_Overview_AfterContent" "Account/Overview/AfterContent.tsx" "$OldComponents_Account_Overview_AfterContent"
PLACE_REACT "$Components_Account_API_BeforeContent" "Account/API/BeforeContent.tsx" "$OldComponents_Account_API_BeforeContent"
PLACE_REACT "$Components_Account_API_AfterContent" "Account/API/AfterContent.tsx" "$OldComponents_Account_API_AfterContent"
PLACE_REACT "$Components_Account_SSH_BeforeContent" "Account/SSH/BeforeContent.tsx" "$OldComponents_Account_SSH_BeforeContent"
PLACE_REACT "$Components_Account_SSH_AfterContent" "Account/SSH/AfterContent.tsx" "$OldComponents_Account_SSH_AfterContent"
else
# warn about missing components.yml file
log_yellow "[WARNING] Could not find '$dashboard_components/Components.yml', component extendability might be limited."
@ -1080,10 +1096,15 @@ if [[ ( $2 == "-r" ) || ( $2 == "-remove" ) ]]; then VCMD="y"
# remove component items
# -> REMOVE_REACT "$Components_" "path/.tsx" "$OldComponents_"
# navigation
REMOVE_REACT "$Components_Navigation_NavigationBar_BeforeNavigation" "Navigation/NavigationBar/BeforeNavigation.tsx"
REMOVE_REACT "$Components_Navigation_NavigationBar_AdditionalItems" "Navigation/NavigationBar/AdditionalItems.tsx"
REMOVE_REACT "$Components_Navigation_NavigationBar_AfterNavigation" "Navigation/NavigationBar/AfterNavigation.tsx"
# server
REMOVE_REACT "$Components_Server_Terminal_BeforeContent" "Server/Terminal/BeforeContent.tsx"
REMOVE_REACT "$Components_Server_Terminal_AfterContent" "Server/Terminal/AfterContent.tsx"
REMOVE_REACT "$Components_Server_Files_Browse_BeforeContent" "Server/Files/Browse/BeforeContent.tsx"

View file

@ -0,0 +1,10 @@
import React from 'react';
/* blueprint/import */
export default () => {
return (
<>
{/* blueprint/react */}
</>
);
};

View file

@ -0,0 +1,10 @@
import React from 'react';
/* blueprint/import */
export default () => {
return (
<>
{/* blueprint/react */}
</>
);
};

View file

@ -0,0 +1,10 @@
import React from 'react';
/* blueprint/import */
export default () => {
return (
<>
{/* blueprint/react */}
</>
);
};

View file

@ -0,0 +1,10 @@
import React from 'react';
/* blueprint/import */
export default () => {
return (
<>
{/* blueprint/react */}
</>
);
};

View file

@ -0,0 +1,10 @@
import React from 'react';
/* blueprint/import */
export default () => {
return (
<>
{/* blueprint/react */}
</>
);
};

View file

@ -0,0 +1,10 @@
import React from 'react';
/* blueprint/import */
export default () => {
return (
<>
{/* blueprint/react */}
</>
);
};

View file

@ -0,0 +1,101 @@
import React, { useEffect, useState } from 'react';
import ContentBox from '@/components/elements/ContentBox';
import CreateApiKeyForm from '@/components/dashboard/forms/CreateApiKeyForm';
import getApiKeys, { ApiKey } from '@/api/account/getApiKeys';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faKey, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import deleteApiKey from '@/api/account/deleteApiKey';
import FlashMessageRender from '@/components/FlashMessageRender';
import { format } from 'date-fns';
import PageContentBlock from '@/components/elements/PageContentBlock';
import tw from 'twin.macro';
import GreyRowBox from '@/components/elements/GreyRowBox';
import { Dialog } from '@/components/elements/dialog';
import { useFlashKey } from '@/plugins/useFlash';
import Code from '@/components/elements/Code';
import BeforeContent from '@/blueprint/components/Account/API/BeforeContent';
import AfterContent from '@/blueprint/components/Account/API/AfterContent';
export default () => {
const [deleteIdentifier, setDeleteIdentifier] = useState('');
const [keys, setKeys] = useState<ApiKey[]>([]);
const [loading, setLoading] = useState(true);
const { clearAndAddHttpError } = useFlashKey('account');
useEffect(() => {
getApiKeys()
.then((keys) => setKeys(keys))
.then(() => setLoading(false))
.catch((error) => clearAndAddHttpError(error));
}, []);
const doDeletion = (identifier: string) => {
setLoading(true);
clearAndAddHttpError();
deleteApiKey(identifier)
.then(() => setKeys((s) => [...(s || []).filter((key) => key.identifier !== identifier)]))
.catch((error) => clearAndAddHttpError(error))
.then(() => {
setLoading(false);
setDeleteIdentifier('');
});
};
return (
<PageContentBlock title={'Account API'}>
<FlashMessageRender byKey={'account'} />
<BeforeContent />
<div css={tw`md:flex flex-nowrap my-10`}>
<ContentBox title={'Create API Key'} css={tw`flex-none w-full md:w-1/2`}>
<CreateApiKeyForm onKeyCreated={(key) => setKeys((s) => [...s!, key])} />
</ContentBox>
<ContentBox title={'API Keys'} css={tw`flex-1 overflow-hidden mt-8 md:mt-0 md:ml-8`}>
<SpinnerOverlay visible={loading} />
<Dialog.Confirm
title={'Delete API Key'}
confirm={'Delete Key'}
open={!!deleteIdentifier}
onClose={() => setDeleteIdentifier('')}
onConfirmed={() => doDeletion(deleteIdentifier)}
>
All requests using the <Code>{deleteIdentifier}</Code> key will be invalidated.
</Dialog.Confirm>
{keys.length === 0 ? (
<p css={tw`text-center text-sm`}>
{loading ? 'Loading...' : 'No API keys exist for this account.'}
</p>
) : (
keys.map((key, index) => (
<GreyRowBox
key={key.identifier}
css={[tw`bg-neutral-600 flex items-center`, index > 0 && tw`mt-2`]}
>
<FontAwesomeIcon icon={faKey} css={tw`text-neutral-300`} />
<div css={tw`ml-4 flex-1 overflow-hidden`}>
<p css={tw`text-sm break-words`}>{key.description}</p>
<p css={tw`text-2xs text-neutral-300 uppercase`}>
Last used:&nbsp;
{key.lastUsedAt ? format(key.lastUsedAt, 'MMM do, yyyy HH:mm') : 'Never'}
</p>
</div>
<p css={tw`text-sm ml-4 hidden md:block`}>
<code css={tw`font-mono py-1 px-2 bg-neutral-900 rounded`}>{key.identifier}</code>
</p>
<button css={tw`ml-4 p-2 text-sm`} onClick={() => setDeleteIdentifier(key.identifier)}>
<FontAwesomeIcon
icon={faTrashAlt}
css={tw`text-neutral-400 hover:text-red-400 transition-colors duration-150`}
/>
</button>
</GreyRowBox>
))
)}
</ContentBox>
</div>
<AfterContent />
</PageContentBlock>
);
};

View file

@ -0,0 +1,58 @@
import * as React from 'react';
import ContentBox from '@/components/elements/ContentBox';
import UpdatePasswordForm from '@/components/dashboard/forms/UpdatePasswordForm';
import UpdateEmailAddressForm from '@/components/dashboard/forms/UpdateEmailAddressForm';
import ConfigureTwoFactorForm from '@/components/dashboard/forms/ConfigureTwoFactorForm';
import PageContentBlock from '@/components/elements/PageContentBlock';
import tw from 'twin.macro';
import { breakpoint } from '@/theme';
import styled from 'styled-components/macro';
import MessageBox from '@/components/MessageBox';
import { useLocation } from 'react-router-dom';
import BeforeContent from '@/blueprint/components/Account/Overview/BeforeContent';
import AfterContent from '@/blueprint/components/Account/Overview/AfterContent';
const Container = styled.div`
${tw`flex flex-wrap`};
& > div {
${tw`w-full`};
${breakpoint('sm')`
width: calc(50% - 1rem);
`}
${breakpoint('md')`
${tw`w-auto flex-1`};
`}
}
`;
export default () => {
const { state } = useLocation<undefined | { twoFactorRedirect?: boolean }>();
return (
<PageContentBlock title={'Account Overview'}>
{state?.twoFactorRedirect && (
<MessageBox title={'2-Factor Required'} type={'error'}>
Your account must have two-factor authentication enabled in order to continue.
</MessageBox>
)}
<BeforeContent />
<Container css={[tw`lg:grid lg:grid-cols-3 mb-10`, state?.twoFactorRedirect ? tw`mt-4` : tw`mt-10`]}>
<ContentBox title={'Update Password'} showFlashes={'account:password'}>
<UpdatePasswordForm />
</ContentBox>
<ContentBox css={tw`mt-8 sm:mt-0 sm:ml-8`} title={'Update Email Address'} showFlashes={'account:email'}>
<UpdateEmailAddressForm />
</ContentBox>
<ContentBox css={tw`md:ml-8 mt-8 md:mt-0`} title={'Two-Step Verification'}>
<ConfigureTwoFactorForm />
</ContentBox>
</Container>
<AfterContent />
</PageContentBlock>
);
};

View file

@ -0,0 +1,68 @@
import React, { useEffect } from 'react';
import ContentBox from '@/components/elements/ContentBox';
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
import FlashMessageRender from '@/components/FlashMessageRender';
import PageContentBlock from '@/components/elements/PageContentBlock';
import tw from 'twin.macro';
import GreyRowBox from '@/components/elements/GreyRowBox';
import { useSSHKeys } from '@/api/account/ssh-keys';
import { useFlashKey } from '@/plugins/useFlash';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faKey } from '@fortawesome/free-solid-svg-icons';
import { format } from 'date-fns';
import CreateSSHKeyForm from '@/components/dashboard/ssh/CreateSSHKeyForm';
import DeleteSSHKeyButton from '@/components/dashboard/ssh/DeleteSSHKeyButton';
import BeforeContent from '@/blueprint/components/Account/SSH/BeforeContent';
import AfterContent from '@/blueprint/components/Account/SSH/AfterContent';
export default () => {
const { clearAndAddHttpError } = useFlashKey('account');
const { data, isValidating, error } = useSSHKeys({
revalidateOnMount: true,
revalidateOnFocus: false,
});
useEffect(() => {
clearAndAddHttpError(error);
}, [error]);
return (
<PageContentBlock title={'SSH Keys'}>
<FlashMessageRender byKey={'account'} />
<BeforeContent />
<div css={tw`md:flex flex-nowrap my-10`}>
<ContentBox title={'Add SSH Key'} css={tw`flex-none w-full md:w-1/2`}>
<CreateSSHKeyForm />
</ContentBox>
<ContentBox title={'SSH Keys'} css={tw`flex-1 overflow-hidden mt-8 md:mt-0 md:ml-8`}>
<SpinnerOverlay visible={!data && isValidating} />
{!data || !data.length ? (
<p css={tw`text-center text-sm`}>
{!data ? 'Loading...' : 'No SSH Keys exist for this account.'}
</p>
) : (
data.map((key, index) => (
<GreyRowBox
key={key.fingerprint}
css={[tw`bg-neutral-600 flex space-x-4 items-center`, index > 0 && tw`mt-2`]}
>
<FontAwesomeIcon icon={faKey} css={tw`text-neutral-300`} />
<div css={tw`flex-1`}>
<p css={tw`text-sm break-words font-medium`}>{key.name}</p>
<p css={tw`text-xs mt-1 font-mono truncate`}>SHA256:{key.fingerprint}</p>
<p css={tw`text-xs mt-1 text-neutral-300 uppercase`}>
Added on:&nbsp;
{format(key.createdAt, 'MMM do, yyyy HH:mm')}
</p>
</div>
<DeleteSSHKeyButton name={key.name} fingerprint={key.fingerprint} />
</GreyRowBox>
))
)}
</ContentBox>
</div>
<AfterContent />
</PageContentBlock>
);
};