feat: reach an initial working stage

This commit is contained in:
Aarnav Tale 2025-03-22 01:36:27 -04:00
parent 8db323b63f
commit 34cfee7cff
34 changed files with 1078 additions and 684 deletions

View file

@ -1,16 +1,12 @@
import { ActionFunctionArgs, data } from 'react-router';
import { hs_getConfig, hs_patchConfig } from '~/utils/config/loader';
import { LoadContext } from '~/server';
import { hp_getIntegration } from '~/utils/integration/loader';
import { auth } from '~/utils/sessions.server';
export async function dnsAction({ request }: ActionFunctionArgs) {
const session = await auth(request);
if (!session) {
return data({ success: false }, 401);
}
const { mode } = hs_getConfig();
if (mode !== 'rw') {
export async function dnsAction({
request,
context,
}: ActionFunctionArgs<LoadContext>) {
if (!context.hs.writable()) {
return data({ success: false }, 403);
}
@ -22,33 +18,33 @@ export async function dnsAction({ request }: ActionFunctionArgs) {
switch (action) {
case 'rename_tailnet':
return renameTailnet(formData);
return renameTailnet(formData, context);
case 'toggle_magic':
return toggleMagic(formData);
return toggleMagic(formData, context);
case 'remove_ns':
return removeNs(formData);
return removeNs(formData, context);
case 'add_ns':
return addNs(formData);
return addNs(formData, context);
case 'remove_domain':
return removeDomain(formData);
return removeDomain(formData, context);
case 'add_domain':
return addDomain(formData);
return addDomain(formData, context);
case 'remove_record':
return removeRecord(formData);
return removeRecord(formData, context);
case 'add_record':
return addRecord(formData);
return addRecord(formData, context);
default:
return data({ success: false }, 400);
}
}
async function renameTailnet(formData: FormData) {
async function renameTailnet(formData: FormData, context: LoadContext) {
const newName = formData.get('new_name')?.toString();
if (!newName) {
return data({ success: false }, 400);
}
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.base_domain',
value: newName,
@ -58,13 +54,13 @@ async function renameTailnet(formData: FormData) {
await hp_getIntegration()?.onConfigChange();
}
async function toggleMagic(formData: FormData) {
async function toggleMagic(formData: FormData, context: LoadContext) {
const newState = formData.get('new_state')?.toString();
if (!newState) {
return data({ success: false }, 400);
}
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.magic_dns',
value: newState === 'enabled',
@ -74,7 +70,8 @@ async function toggleMagic(formData: FormData) {
await hp_getIntegration()?.onConfigChange();
}
async function removeNs(formData: FormData) {
async function removeNs(formData: FormData, context: LoadContext) {
const config = context.hs.c!;
const ns = formData.get('ns')?.toString();
const splitName = formData.get('split_name')?.toString();
@ -82,15 +79,10 @@ async function removeNs(formData: FormData) {
return data({ success: false }, 400);
}
const { config, mode } = hs_getConfig();
if (mode !== 'rw') {
return data({ success: false }, 403);
}
if (splitName === 'global') {
const servers = config.dns.nameservers.global.filter((i) => i !== ns);
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.nameservers.global',
value: servers,
@ -100,7 +92,7 @@ async function removeNs(formData: FormData) {
const splits = config.dns.nameservers.split;
const servers = splits[splitName].filter((i) => i !== ns);
await hs_patchConfig([
await context.hs.patch([
{
path: `dns.nameservers.split."${splitName}"`,
value: servers,
@ -111,7 +103,8 @@ async function removeNs(formData: FormData) {
await hp_getIntegration()?.onConfigChange();
}
async function addNs(formData: FormData) {
async function addNs(formData: FormData, context: LoadContext) {
const config = context.hs.c!;
const ns = formData.get('ns')?.toString();
const splitName = formData.get('split_name')?.toString();
@ -119,16 +112,11 @@ async function addNs(formData: FormData) {
return data({ success: false }, 400);
}
const { config, mode } = hs_getConfig();
if (mode !== 'rw') {
return data({ success: false }, 403);
}
if (splitName === 'global') {
const servers = config.dns.nameservers.global;
servers.push(ns);
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.nameservers.global',
value: servers,
@ -139,7 +127,7 @@ async function addNs(formData: FormData) {
const servers = splits[splitName] ?? [];
servers.push(ns);
await hs_patchConfig([
await context.hs.patch([
{
path: `dns.nameservers.split."${splitName}"`,
value: servers,
@ -150,20 +138,15 @@ async function addNs(formData: FormData) {
await hp_getIntegration()?.onConfigChange();
}
async function removeDomain(formData: FormData) {
async function removeDomain(formData: FormData, context: LoadContext) {
const config = context.hs.c!;
const domain = formData.get('domain')?.toString();
if (!domain) {
return data({ success: false }, 400);
}
const { config, mode } = hs_getConfig();
if (mode !== 'rw') {
return data({ success: false }, 403);
}
const domains = config.dns.search_domains.filter((i) => i !== domain);
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.search_domains',
value: domains,
@ -173,21 +156,17 @@ async function removeDomain(formData: FormData) {
await hp_getIntegration()?.onConfigChange();
}
async function addDomain(formData: FormData) {
async function addDomain(formData: FormData, context: LoadContext) {
const config = context.hs.c!;
const domain = formData.get('domain')?.toString();
if (!domain) {
return data({ success: false }, 400);
}
const { config, mode } = hs_getConfig();
if (mode !== 'rw') {
return data({ success: false }, 403);
}
const domains = config.dns.search_domains;
domains.push(domain);
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.search_domains',
value: domains,
@ -197,7 +176,8 @@ async function addDomain(formData: FormData) {
await hp_getIntegration()?.onConfigChange();
}
async function removeRecord(formData: FormData) {
async function removeRecord(formData: FormData, context: LoadContext) {
const config = context.hs.c!;
const recordName = formData.get('record_name')?.toString();
const recordType = formData.get('record_type')?.toString();
@ -205,16 +185,11 @@ async function removeRecord(formData: FormData) {
return data({ success: false }, 400);
}
const { config, mode } = hs_getConfig();
if (mode !== 'rw') {
return data({ success: false }, 403);
}
const records = config.dns.extra_records.filter(
(i) => i.name !== recordName || i.type !== recordType,
);
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.extra_records',
value: records,
@ -224,7 +199,8 @@ async function removeRecord(formData: FormData) {
await hp_getIntegration()?.onConfigChange();
}
async function addRecord(formData: FormData) {
async function addRecord(formData: FormData, context: LoadContext) {
const config = context.hs.c!;
const recordName = formData.get('record_name')?.toString();
const recordType = formData.get('record_type')?.toString();
const recordValue = formData.get('record_value')?.toString();
@ -233,15 +209,10 @@ async function addRecord(formData: FormData) {
return data({ success: false }, 400);
}
const { config, mode } = hs_getConfig();
if (mode !== 'rw') {
return data({ success: false }, 403);
}
const records = config.dns.extra_records;
records.push({ name: recordName, type: recordType, value: recordValue });
await hs_patchConfig([
await context.hs.patch([
{
path: 'dns.extra_records',
value: records,

View file

@ -1,8 +1,8 @@
import type { ActionFunctionArgs } from 'react-router';
import type { ActionFunctionArgs, LoaderFunctionArgs } from 'react-router';
import { useLoaderData } from 'react-router';
import Code from '~/components/Code';
import Notice from '~/components/Notice';
import { hs_getConfig } from '~/utils/config/loader';
import type { LoadContext } from '~/server';
import ManageDomains from './components/manage-domains';
import ManageNS from './components/manage-ns';
import ManageRecords from './components/manage-records';
@ -11,12 +11,12 @@ import ToggleMagic from './components/toggle-magic';
import { dnsAction } from './dns-actions';
// We do not want to expose every config value
export async function loader() {
const { config, mode } = hs_getConfig();
if (mode === 'no') {
export async function loader({ context }: LoaderFunctionArgs<LoadContext>) {
if (!context.hs.readable()) {
throw new Error('No configuration is available');
}
const config = context.hs.c!;
const dns = {
prefixes: config.prefixes,
magicDns: config.dns.magic_dns,
@ -29,7 +29,7 @@ export async function loader() {
return {
...dns,
mode,
writable: context.hs.writable(),
};
}
@ -46,11 +46,11 @@ export default function Page() {
}
allNs.global = data.nameservers;
const isDisabled = data.mode !== 'rw';
const isDisabled = data.writable === false;
return (
<div className="flex flex-col gap-16 max-w-screen-lg">
{data.mode === 'rw' ? undefined : (
{data.writable ? undefined : (
<Notice>
The Headscale configuration is read-only. You cannot make changes to
the configuration