feat core
react
: Add account pages to supported Components
This commit is contained in:
parent
5d80d70974
commit
456145cba4
10 changed files with 308 additions and 0 deletions
21
blueprint.sh
21
blueprint.sh
|
@ -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"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
/* blueprint/import */
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
{/* blueprint/react */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
/* blueprint/import */
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
{/* blueprint/react */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
/* blueprint/import */
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
{/* blueprint/react */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
/* blueprint/import */
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
{/* blueprint/react */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
/* blueprint/import */
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
{/* blueprint/react */}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react';
|
||||
/* blueprint/import */
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<>
|
||||
{/* blueprint/react */}
|
||||
</>
|
||||
);
|
||||
};
|
101
resources/scripts/components/dashboard/AccountApiContainer.tsx
Normal file
101
resources/scripts/components/dashboard/AccountApiContainer.tsx
Normal 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:
|
||||
{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>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -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:
|
||||
{format(key.createdAt, 'MMM do, yyyy HH:mm')}
|
||||
</p>
|
||||
</div>
|
||||
<DeleteSSHKeyButton name={key.name} fingerprint={key.fingerprint} />
|
||||
</GreyRowBox>
|
||||
))
|
||||
)}
|
||||
</ContentBox>
|
||||
</div>
|
||||
<AfterContent />
|
||||
</PageContentBlock>
|
||||
);
|
||||
};
|
Loading…
Reference in a new issue