Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions app/components/admin/admin-metric-card.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InfoIcon } from '@chakra-ui/icons';
import { CardBody, Card, Text, Flex, Tooltip } from '@chakra-ui/react';
import { Card, Text, Flex, Icon } from '@chakra-ui/react';
import { Tooltip } from '~/components/ui/tooltip';
import { FaCircleInfo } from 'react-icons/fa6';
import type { IconType } from 'react-icons';

interface AdminMetricCardProps {
Expand All @@ -16,8 +17,8 @@ export default function AdminMetricCard({
tooltipText,
}: AdminMetricCardProps) {
return (
<Card w={{ sm: '100%', md: 300 }} minW={270} maxW={400} mr={{ sm: '0', md: '4' }} mt="4">
<CardBody flexDirection="row" display="flex" justifyContent="space-between">
<Card.Root w={{ sm: '100%', md: 300 }} minW={270} maxW={400} mr={{ sm: '0', md: '4' }} mt="4">
<Card.Body flexDirection="row" display="flex" justifyContent="space-between">
<Flex alignItems="center">
<Flex
background="#d9d9d9"
Expand All @@ -39,10 +40,12 @@ export default function AdminMetricCard({
</Text>
</Flex>
</Flex>
<Tooltip label={tooltipText}>
<InfoIcon color="#d9d9d9" fontSize="xl" />
<Tooltip content={tooltipText}>
<Icon color="#d9d9d9" fontSize="xl">
<FaCircleInfo />
</Icon>
</Tooltip>
</CardBody>
</Card>
</Card.Body>
</Card.Root>
);
}
167 changes: 74 additions & 93 deletions app/components/admin/users-table.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,7 @@
import {
Table,
Tr,
Th,
Thead,
Tbody,
TableContainer,
Td,
Card,
IconButton,
Tooltip,
Flex,
HStack,
Spinner,
Text,
} from '@chakra-ui/react';
import { DeleteIcon } from '@chakra-ui/icons';
import { FaTheaterMasks } from 'react-icons/fa';
import { Table, Card, IconButton, Flex, HStack, Spinner, Text } from '@chakra-ui/react';
import { FaRegTrashCan, FaMasksTheater } from 'react-icons/fa6';
import { Form, useNavigation } from '@remix-run/react';
import { Tooltip } from '~/components/ui/tooltip';

import type { UserWithMetrics } from '~/routes/_auth.admin._index';
import { MIN_USERS_SEARCH_TEXT } from '~/routes/_auth.admin._index';
Expand All @@ -41,83 +26,79 @@ export default function UsersTable({ users, searchText }: UsersTableProps) {
const shouldShowUsers = !(isLoading || shouldShowInstruction || shouldShowNoUsersMessage);

return (
<Card p="2" mt="4">
<TableContainer>
<Table variant="striped" colorScheme="gray">
<Thead>
<Tr>
<Th>Email</Th>
<Th>Name</Th>
<Th>DNS Records</Th>
<Th>Certificate status</Th>
<Th />
</Tr>
</Thead>
<Tbody>
{!shouldShowUsers && (
<Tr>
<Td py="8" colSpan={7}>
<Flex justifyContent="center">
{isLoading && <Spinner />}
{shouldShowInstruction && !isLoading && (
<Text>Please enter at least 3 characters to search</Text>
)}
{shouldShowNoUsersMessage && !isLoading && <Text>No users found</Text>}
</Flex>
</Td>
</Tr>
)}
<Card.Root variant="elevated" p="2" mt="4" style={{ overflowY: 'auto' }}>
<Table.Root colorScheme="gray">
<Table.Header>
<Table.Row>
<Table.ColumnHeader>Email</Table.ColumnHeader>
<Table.ColumnHeader>Name</Table.ColumnHeader>
<Table.ColumnHeader>DNS Records</Table.ColumnHeader>
<Table.ColumnHeader>Certificate status</Table.ColumnHeader>
<Table.ColumnHeader />
</Table.Row>
</Table.Header>
<Table.Body>
{!shouldShowUsers && (
<Table.Row>
<Table.Cell py="8" colSpan={7}>
<Flex justifyContent="center">
{isLoading && <Spinner />}
{shouldShowInstruction && !isLoading && (
<Text>Please enter at least 3 characters to search</Text>
)}
{shouldShowNoUsersMessage && !isLoading && <Text>No users found</Text>}
</Flex>
</Table.Cell>
</Table.Row>
)}

{shouldShowUsers &&
users.map((user) => {
return (
<Tr key={user.email}>
<Td>{user.email}</Td>
<Td>{user.displayName}</Td>
<Td>{user.dnsRecordCount}</Td>
<Td>
<CertificateStatusIcon status={user.certificate?.status} />
</Td>
<Td>
<HStack>
<Tooltip label="Impersonate user">
<Form method="post">
<input
type="hidden"
name="newEffectiveUsername"
value={user.username}
/>
<input type="hidden" name="intent" value="impersonate-user" />
<IconButton
type="submit"
aria-label="Impersonate user"
icon={<FaTheaterMasks color="black" size={24} />}
variant="ghost"
isDisabled={user.username === username}
/>
</Form>
</Tooltip>
{shouldShowUsers &&
users.map((user) => {
return (
<Table.Row key={user.email}>
<Table.Cell>{user.email}</Table.Cell>
<Table.Cell>{user.displayName}</Table.Cell>
<Table.Cell>{user.dnsRecordCount}</Table.Cell>
<Table.Cell>
<CertificateStatusIcon status={user.certificate?.status} />
</Table.Cell>
<Table.Cell>
<HStack>
<Tooltip content="Impersonate user">
<Form method="post">
<Tooltip label="Delete user">
<IconButton
aria-label="Delete user"
icon={<DeleteIcon color="black" boxSize={5} />}
variant="ghost"
isDisabled={user.username === username}
type="submit"
/>
</Tooltip>
<input type="hidden" name="username" value={user.username} />
<input type="hidden" name="intent" value="delete-user" />
<input type="hidden" name="newEffectiveUsername" value={user.username} />
<input type="hidden" name="intent" value="impersonate-user" />
<IconButton
type="submit"
aria-label="Impersonate user"
variant="ghost"
disabled={user.username === username}
>
<FaMasksTheater color="black" size={24} />
</IconButton>
</Form>
</HStack>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</TableContainer>
</Card>
</Tooltip>
<Form method="post">
<Tooltip content="Delete user">
<IconButton
aria-label="Delete user"
variant="ghost"
disabled={user.username === username}
type="submit"
>
<FaRegTrashCan color="black" size={24} />
</IconButton>
</Tooltip>
<input type="hidden" name="username" value={user.username} />
<input type="hidden" name="intent" value="delete-user" />
</Form>
</HStack>
</Table.Cell>
</Table.Row>
);
})}
</Table.Body>
</Table.Root>
</Card.Root>
);
}
83 changes: 36 additions & 47 deletions app/components/certificate/certificate-available.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,4 @@
import {
Heading,
Tab,
TabList,
Tabs,
TabPanels,
Text,
OrderedList,
ListItem,
} from '@chakra-ui/react';
import { Heading, Tabs, Text, List } from '@chakra-ui/react';
import { useMemo } from 'react';
import dayjs from 'dayjs';

Expand Down Expand Up @@ -57,57 +48,55 @@ export default function CertificateAvailable({
will require you to use them in various combinations. These parts include:
</Text>

<OrderedList maxW={750}>
<ListItem>
<List.Root maxW={750} as="ol">
<List.Item>
<strong>Public Certificate</strong>: public (not secret) certificate that verifies domain
ownership
</ListItem>
<ListItem>
</List.Item>
<List.Item>
<strong>Private Key</strong>: private code (do not share!) used to encrypt/decrypt data
</ListItem>
<ListItem>
</List.Item>
<List.Item>
<strong>Intermediate Certificate Chain</strong>: set of one or more certificates that link
your certificate back to a Certificate Authority, establishing a trust relationship
</ListItem>
<ListItem>
</List.Item>
<List.Item>
<strong>Full Chain</strong>: your Public Certificate combined with the Intermediate
Certificate Chain
</ListItem>
</OrderedList>
</List.Item>
</List.Root>

<Text maxW={750}>
You can select from the list of pre-defined services below to help guide you, or see a
General view for all of the certificate's parts.
</Text>

<Tabs maxW={750}>
<TabList>
<Tab>General</Tab>
<Tab>Node.js</Tab>
<Tab>AWS</Tab>
<Tab>NGINX</Tab>
</TabList>
<Tabs.Root maxW={750} defaultValue="general">
<Tabs.List>
<Tabs.Trigger value="general">General</Tabs.Trigger>
<Tabs.Trigger value="node">Node.js</Tabs.Trigger>
<Tabs.Trigger value="aws">AWS</Tabs.Trigger>
<Tabs.Trigger value="nginx">NGINX</Tabs.Trigger>
</Tabs.List>

<TabPanels>
<GeneralPanel
certificate={certificate.certificate!}
privateKey={certificate.privateKey!}
chain={certificate.chain!}
fullChain={certificate.fullChain!}
/>
<NodePanel
certificate={certificate.certificate!}
privateKey={certificate.privateKey!}
chain={certificate.chain!}
/>
<AwsPanel
certificate={certificate.certificate!}
privateKey={certificate.privateKey!}
chain={certificate.chain!}
/>
<NginxPanel privateKey={certificate.privateKey!} fullChain={certificate.fullChain!} />
</TabPanels>
</Tabs>
<GeneralPanel
certificate={certificate.certificate!}
privateKey={certificate.privateKey!}
chain={certificate.chain!}
fullChain={certificate.fullChain!}
/>
<NodePanel
certificate={certificate.certificate!}
privateKey={certificate.privateKey!}
chain={certificate.chain!}
/>
<AwsPanel
certificate={certificate.certificate!}
privateKey={certificate.privateKey!}
chain={certificate.chain!}
/>
<NginxPanel privateKey={certificate.privateKey!} fullChain={certificate.fullChain!} />
</Tabs.Root>
</>
);
}
Loading