/* eslint-disable @typescript-eslint/no-non-null-assertion */ import { BeakerIcon, EyeIcon, IssueDraftIcon, PencilIcon } from '@primer/octicons-react' import { ActionFunctionArgs, json, LoaderFunctionArgs } from '@remix-run/node' import { useFetcher, useLoaderData } from '@remix-run/react' import { useEffect, useState } from 'react' import { Tab, TabList, TabPanel, Tabs } from 'react-aria-components' import { setTimeout } from 'node:timers/promises' import Button from '~/components/Button' import Code from '~/components/Code' import Link from '~/components/Link' import Notice from '~/components/Notice' import Spinner from '~/components/Spinner' import { toast } from '~/components/Toaster' import { cn } from '~/utils/cn' import { loadContext } from '~/utils/config/headplane' import { HeadscaleError, pull, put } from '~/utils/headscale' import { getSession } from '~/utils/sessions' import Monaco from './editor' export async function loader({ request }: LoaderFunctionArgs) { const session = await getSession(request.headers.get('Cookie')) try { const { policy } = await pull<{ policy: string }>( 'v1/policy', session.get('hsApiKey')!, ) try { // We have read access, now do we have write access? // Attempt to set the policy to what we just got await put('v1/policy', session.get('hsApiKey')!, { policy, }) return { hasAclWrite: true, currentAcl: policy, aclType: 'json', } as const } catch (error) { if (!(error instanceof HeadscaleError)) { throw error } if (error.status === 500) { return { hasAclWrite: false, currentAcl: policy, aclType: 'json', } as const } } } catch {} return { hasAclWrite: true, currentAcl: '', aclType: 'json', } as const } export async function action({ request }: ActionFunctionArgs) { const session = await getSession(request.headers.get('Cookie')) if (!session.has('hsApiKey')) { return json({ success: false }, { status: 401, }) } const { acl } = await request.json() as { acl: string, api: boolean } try { await put('v1/policy', session.get('hsApiKey')!, { policy: acl, }) await setTimeout(250) return json({ success: true }) } catch (error) { return json({ success: false }, { status: error instanceof HeadscaleError ? error.status : 500, }) } return json({ success: true }) } export function ErrorBoundary() { return (
The ACL file is used to define the access control rules for your network. You can find more information about the ACL file in the {' '} Tailscale ACL guide {' '} and the {' '} Headscale docs .
If you are running Headscale 0.23-beta1 or later, the
ACL configuration is most likely set to
{' '}
file
{' '}
mode but the ACL file is not available. In order to
resolve this you will either need to correctly set
{' '}
policy.path
{' '}
in your Headscale configuration or set the
{' '}
policy.mode
{' '}
to
{' '}
database
.
The ACL file is used to define the access control rules for your network. You can find more information about the ACL file in the {' '} Tailscale ACL guide {' '} and the {' '} Headscale docs .
Edit file
Preview changes
Preview rules
The Preview rules is very much still a work in progress. It is a bit complicated to implement right now but hopefully it will be available soon.