feat: update to the v8 middleware api

This commit is contained in:
Aarnav Tale
2026-06-20 12:38:09 -04:00
parent 3f9dcd5eb4
commit 5d6eef5843
30 changed files with 467 additions and 239 deletions
+38 -27
View File
@@ -1,18 +1,29 @@
import { data } from "react-router";
import {
authContext,
headscaleConfigContext,
headscaleContext,
integrationContext,
} from "~/server/context";
import { Capabilities } from "~/server/web/roles";
import type { Route } from "./+types/overview";
export async function dnsAction({ request, context }: Route.ActionArgs) {
const principal = await context.auth.require(request);
const check = context.auth.can(principal, Capabilities.write_network);
const auth = context.get(authContext);
const headscale = context.get(headscaleContext);
const headscaleConfig = context.get(headscaleConfigContext);
const integration = context.get(integrationContext);
const principal = await auth.require(request);
const check = auth.can(principal, Capabilities.write_network);
if (!check) {
return data({ success: false }, 403);
}
if (!context.hs.writable()) {
if (!headscaleConfig.writable()) {
return data({ success: false }, 403);
}
@@ -29,14 +40,14 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
return data({ success: false }, 400);
}
await context.hs.patch([
await headscaleConfig.patch([
{
path: "dns.base_domain",
value: newName,
},
]);
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "Tailnet renamed successfully" };
}
case "toggle_magic": {
@@ -45,18 +56,18 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
return data({ success: false }, 400);
}
await context.hs.patch([
await headscaleConfig.patch([
{
path: "dns.magic_dns",
value: newState === "enabled",
},
]);
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "Magic DNS state updated successfully" };
}
case "remove_ns": {
const config = context.hs.c!;
const config = headscaleConfig.c!;
const ns = formData.get("ns")?.toString();
const splitName = formData.get("split_name")?.toString();
@@ -67,7 +78,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
if (splitName === "global") {
const servers = config.dns.nameservers.global.filter((i) => i !== ns);
await context.hs.patch([
await headscaleConfig.patch([
{
path: "dns.nameservers.global",
value: servers,
@@ -77,7 +88,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
const splits = config.dns.nameservers.split;
const servers = splits[splitName].filter((i) => i !== ns);
await context.hs.patch([
await headscaleConfig.patch([
{
path: `dns.nameservers.split."${splitName}"`,
value: servers.length > 0 ? servers : null,
@@ -85,11 +96,11 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
]);
}
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "Nameserver removed successfully" };
}
case "add_ns": {
const config = context.hs.c!;
const config = headscaleConfig.c!;
const ns = formData.get("ns")?.toString();
const splitName = formData.get("split_name")?.toString();
@@ -101,7 +112,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
const servers = config.dns.nameservers.global;
servers.push(ns);
await context.hs.patch([
await headscaleConfig.patch([
{
path: "dns.nameservers.global",
value: servers,
@@ -112,7 +123,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
const servers = splits[splitName] ?? [];
servers.push(ns);
await context.hs.patch([
await headscaleConfig.patch([
{
path: `dns.nameservers.split."${splitName}"`,
value: servers,
@@ -120,29 +131,29 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
]);
}
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "Nameserver added successfully" };
}
case "remove_domain": {
const config = context.hs.c!;
const config = headscaleConfig.c!;
const domain = formData.get("domain")?.toString();
if (!domain) {
return data({ success: false }, 400);
}
const domains = config.dns.search_domains.filter((i) => i !== domain);
await context.hs.patch([
await headscaleConfig.patch([
{
path: "dns.search_domains",
value: domains,
},
]);
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "Domain removed successfully" };
}
case "add_domain": {
const config = context.hs.c!;
const config = headscaleConfig.c!;
const domain = formData.get("domain")?.toString();
if (!domain) {
return data({ success: false }, 400);
@@ -151,14 +162,14 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
const domains = config.dns.search_domains;
domains.push(domain);
await context.hs.patch([
await headscaleConfig.patch([
{
path: "dns.search_domains",
value: domains,
},
]);
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "Domain added successfully" };
}
case "remove_record": {
@@ -170,7 +181,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
}
// Value is not needed for removal
const restart = await context.hs.removeDNS({
const restart = await headscaleConfig.removeDNS({
name: recordName,
type: recordType,
value: "",
@@ -180,7 +191,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
return;
}
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "DNS record removed successfully" };
}
case "add_record": {
@@ -192,7 +203,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
return data({ success: false }, 400);
}
const restart = await context.hs.addDNS({
const restart = await headscaleConfig.addDNS({
name: recordName,
type: recordType,
value: recordValue,
@@ -202,7 +213,7 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
return;
}
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "DNS record added successfully" };
}
case "override_dns": {
@@ -212,14 +223,14 @@ export async function dnsAction({ request, context }: Route.ActionArgs) {
}
const overrideValue = override === "true";
await context.hs.patch([
await headscaleConfig.patch([
{
path: "dns.override_local_dns",
value: overrideValue,
},
]);
await context.integration?.onConfigChange(context.headscale);
await integration?.onConfigChange(headscale);
return { message: "DNS override updated successfully" };
}
default:
+14 -10
View File
@@ -1,12 +1,13 @@
import type { ActionFunctionArgs, LoaderFunctionArgs } from "react-router";
import type { ActionFunctionArgs } from "react-router";
import { useLoaderData } from "react-router";
import Code from "~/components/code";
import Notice from "~/components/notice";
import PageError from "~/components/page-error";
import type { AppContext } from "~/server/context";
import { authContext, headscaleConfigContext } from "~/server/context";
import { Capabilities } from "~/server/web/roles";
import type { Route } from "./+types/overview";
import ManageDomains from "./components/manage-domains";
import ManageNS from "./components/manage-ns";
import ManageRecords from "./components/manage-records";
@@ -15,13 +16,16 @@ import ToggleMagic from "./components/toggle-magic";
import { dnsAction } from "./dns-actions";
// We do not want to expose every config value
export async function loader({ request, context }: LoaderFunctionArgs<AppContext>) {
if (!context.hs.readable()) {
export async function loader({ request, context }: Route.LoaderArgs) {
const auth = context.get(authContext);
const headscaleConfig = context.get(headscaleConfigContext);
if (!headscaleConfig.readable()) {
throw new Error("No configuration is available");
}
const principal = await context.auth.require(request);
const check = context.auth.can(principal, Capabilities.read_network);
const principal = await auth.require(request);
const check = auth.can(principal, Capabilities.read_network);
if (!check) {
// Not authorized to view this page
throw new Error(
@@ -29,9 +33,9 @@ export async function loader({ request, context }: LoaderFunctionArgs<AppContext
);
}
const writablePermission = context.auth.can(principal, Capabilities.write_network);
const writablePermission = auth.can(principal, Capabilities.write_network);
const config = context.hs.c!;
const config = headscaleConfig.c!;
const dns = {
prefixes: config.prefixes,
magicDns: config.dns.magic_dns,
@@ -40,13 +44,13 @@ export async function loader({ request, context }: LoaderFunctionArgs<AppContext
splitDns: config.dns.nameservers.split,
searchDomains: config.dns.search_domains,
overrideDns: config.dns.override_local_dns,
extraRecords: context.hs.d,
extraRecords: headscaleConfig.d,
};
return {
...dns,
access: writablePermission,
writable: context.hs.writable(),
writable: headscaleConfig.writable(),
};
}