feat: refactor all dashboard routes
This commit is contained in:
parent
50e43bc0c3
commit
6cf343d623
39 changed files with 48 additions and 66 deletions
122
app/routes/acls/components/cm.client.tsx
Normal file
122
app/routes/acls/components/cm.client.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import Merge from 'react-codemirror-merge'
|
||||
import CodeMirror from '@uiw/react-codemirror'
|
||||
import * as shopify from '@shopify/lang-jsonc'
|
||||
import { ClientOnly } from 'remix-utils/client-only'
|
||||
import { ErrorBoundary } from 'react-error-boundary'
|
||||
import { githubDark, githubLight } from '@uiw/codemirror-theme-github'
|
||||
import { useState } from 'react'
|
||||
import { cn } from '~/utils/cn'
|
||||
|
||||
import Fallback from './fallback'
|
||||
|
||||
interface EditorProps {
|
||||
isDisabled?: boolean
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
|
||||
export function Editor(props: EditorProps) {
|
||||
const [light, setLight] = useState(false)
|
||||
useEffect(() => {
|
||||
const theme = window.matchMedia('(prefers-color-scheme: light)')
|
||||
setLight(theme.matches)
|
||||
theme.addEventListener('change', (theme) => {
|
||||
setLight(theme.matches)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'border border-gray-200 dark:border-gray-700',
|
||||
'rounded-b-lg rounded-tr-lg mb-2 z-10 overflow-x-hidden',
|
||||
)}>
|
||||
<div className="overflow-y-scroll h-editor text-sm">
|
||||
<ErrorBoundary fallback={
|
||||
<p className={cn(
|
||||
'w-full h-full flex items-center justify-center',
|
||||
'text-gray-400 dark:text-gray-500 text-xl',
|
||||
)}>
|
||||
Failed to load the editor.
|
||||
</p>
|
||||
}>
|
||||
<ClientOnly fallback={<Fallback acl={props.value} />}>
|
||||
{() => (
|
||||
<CodeMirror
|
||||
value={props.value}
|
||||
height="100%"
|
||||
extensions={[shopify.jsonc()]}
|
||||
style={{ height: "100%" }}
|
||||
theme={light ? githubLight : githubDark}
|
||||
onChange={(value) => props.onChange(value)}
|
||||
/>
|
||||
)}
|
||||
</ClientOnly>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
interface DifferProps {
|
||||
left: string
|
||||
right: string
|
||||
}
|
||||
|
||||
export function Differ(props: DifferProps) {
|
||||
const [light, setLight] = useState(false)
|
||||
useEffect(() => {
|
||||
const theme = window.matchMedia('(prefers-color-scheme: light)')
|
||||
setLight(theme.matches)
|
||||
theme.addEventListener('change', (theme) => {
|
||||
setLight(theme.matches)
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'border border-gray-200 dark:border-gray-700',
|
||||
'rounded-b-lg rounded-tr-lg mb-2 z-10 overflow-x-hidden',
|
||||
)}>
|
||||
<div className="overflow-y-scroll h-editor text-sm">
|
||||
{props.left === props.right ? (
|
||||
<p className={cn(
|
||||
'w-full h-full flex items-center justify-center',
|
||||
'text-gray-400 dark:text-gray-500 text-xl',
|
||||
)}>
|
||||
No changes
|
||||
</p>
|
||||
) : (
|
||||
<ErrorBoundary fallback={
|
||||
<p className={cn(
|
||||
'w-full h-full flex items-center justify-center',
|
||||
'text-gray-400 dark:text-gray-500 text-xl',
|
||||
)}>
|
||||
Failed to load the editor.
|
||||
</p>
|
||||
}>
|
||||
<ClientOnly fallback={<Fallback acl={props.right} />}>
|
||||
{() => (
|
||||
<Merge
|
||||
orientation="a-b"
|
||||
theme={light ? githubLight : githubDark}
|
||||
>
|
||||
<Merge.Original
|
||||
readOnly
|
||||
value={props.left}
|
||||
extensions={[shopify.jsonc()]}
|
||||
/>
|
||||
<Merge.Modified
|
||||
readOnly
|
||||
value={props.right}
|
||||
extensions={[shopify.jsonc()]}
|
||||
/>
|
||||
</Merge>
|
||||
)}
|
||||
</ClientOnly>
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
30
app/routes/acls/components/error.tsx
Normal file
30
app/routes/acls/components/error.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { cn } from '~/utils/cn'
|
||||
import { AlertIcon } from '@primer/octicons-react'
|
||||
|
||||
import Card from '~/components/Card'
|
||||
import Code from '~/components/Code'
|
||||
|
||||
interface Props {
|
||||
message: string
|
||||
}
|
||||
|
||||
export function ErrorView({ message }: Props) {
|
||||
return (
|
||||
<Card variant="flat" className="max-w-full mb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Card.Title className="text-xl mb-0">
|
||||
Error
|
||||
</Card.Title>
|
||||
<AlertIcon className="w-8 h-8 text-red-500"/>
|
||||
</div>
|
||||
<Card.Text className="mt-4">
|
||||
Could not apply changes to your ACL policy
|
||||
due to the following error:
|
||||
<br />
|
||||
<Code>
|
||||
{message}
|
||||
</Code>
|
||||
</Card.Text>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
24
app/routes/acls/components/fallback.tsx
Normal file
24
app/routes/acls/components/fallback.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import Spinner from '~/components/Spinner'
|
||||
import { cn } from '~/utils/cn'
|
||||
|
||||
interface Props {
|
||||
readonly acl: string
|
||||
}
|
||||
|
||||
export default function Fallback({ acl }: Props) {
|
||||
return (
|
||||
<div className="inline-block relative w-full h-editor">
|
||||
<Spinner className="w-4 h-4 absolute p-2" />
|
||||
<textarea
|
||||
readOnly
|
||||
className={cn(
|
||||
'w-full h-editor font-mono resize-none',
|
||||
'text-sm text-gray-600 dark:text-gray-300',
|
||||
'bg-ui-100 dark:bg-ui-800',
|
||||
'pl-10 pt-1 leading-snug',
|
||||
)}
|
||||
value={acl}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
43
app/routes/acls/components/unavailable.tsx
Normal file
43
app/routes/acls/components/unavailable.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { cn } from '~/utils/cn'
|
||||
import { AlertIcon } from '@primer/octicons-react'
|
||||
|
||||
import Code from '~/components/Code'
|
||||
import Card from '~/components/Card'
|
||||
|
||||
interface Props {
|
||||
mode: 'file' | 'database'
|
||||
}
|
||||
|
||||
export function Unavailable({ mode }: Props) {
|
||||
return (
|
||||
<Card variant="flat" className="max-w-prose mt-12">
|
||||
<div className="flex items-center justify-between">
|
||||
<Card.Title className="text-xl mb-0">
|
||||
ACL Policy Unavailable
|
||||
</Card.Title>
|
||||
<AlertIcon className="w-8 h-8 text-red-500"/>
|
||||
</div>
|
||||
<Card.Text className="mt-4">
|
||||
Unable to load a valid ACL policy configuration.
|
||||
This is most likely due to a misconfiguration in your
|
||||
Headscale configuration file.
|
||||
</Card.Text>
|
||||
|
||||
{mode !== 'file' ? (
|
||||
<p className="mt-4 text-sm">
|
||||
According to your configuration, the ACL policy mode
|
||||
is set to <Code>file</Code> but the ACL file is not
|
||||
available. Ensure that the <Code>policy.path</Code> is
|
||||
set to a valid path in your Headscale configuration.
|
||||
</p>
|
||||
) : (
|
||||
<p className="mt-4 text-sm">
|
||||
In order to fully utilize the ACL management features of
|
||||
Headplane, please set <Code>policy.mode</Code> to either
|
||||
{' '}<Code>file</Code> or <Code>database</Code> in your
|
||||
Headscale configuration.
|
||||
</p>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue