diff --git a/app/routes/_data.acls._index/editor.tsx b/app/routes/_data.acls._index/editor.tsx new file mode 100644 index 0000000..f310cf1 --- /dev/null +++ b/app/routes/_data.acls._index/editor.tsx @@ -0,0 +1,113 @@ +import { json } from '@codemirror/lang-json' +import { useFetcher } from '@remix-run/react' +import { githubDark, githubLight } from '@uiw/codemirror-theme-github' +import CodeMirror from '@uiw/react-codemirror' +import clsx from 'clsx' +import { useEffect, useState } from 'react' +import CodeMirrorMerge from 'react-codemirror-merge' +import { toast } from 'react-hot-toast/headless' + +import Button from '~/components/Button' +import Spinner from '~/components/Spinner' + +type EditorProperties = { + readonly acl: string; + readonly setAcl: (acl: string) => void; + readonly mode: 'edit' | 'diff'; + + readonly data: { + hasAclWrite: boolean; + currentAcl: string; + }; +} + +export default function Editor({ data, acl, setAcl, mode }: EditorProperties) { + const [light, setLight] = useState(false) + const fetcher = useFetcher() + + useEffect(() => { + const theme = window.matchMedia('(prefers-color-scheme: light)') + setLight(theme.matches) + + theme.addEventListener('change', theme => { + setLight(theme.matches) + }) + }, []) + + return ( + + <> +
+ {mode === 'edit' ? ( + { + setAcl(value) + }} + /> + ) : ( +
+ + + + +
+ )} +
+ + + + + ) +} diff --git a/app/routes/_data.acls._index/route.tsx b/app/routes/_data.acls._index/route.tsx index 5dfdcce..b0826ff 100644 --- a/app/routes/_data.acls._index/route.tsx +++ b/app/routes/_data.acls._index/route.tsx @@ -1,13 +1,180 @@ -import { CubeTransparentIcon } from '@heroicons/react/24/outline' +import { Tab } from '@headlessui/react' +import { BeakerIcon, CubeTransparentIcon, EyeIcon, PencilSquareIcon } from '@heroicons/react/24/outline' +import { type ActionFunctionArgs, json } from '@remix-run/node' +import { useLoaderData } from '@remix-run/react' +import clsx from 'clsx' +import { useState } from 'react' +import { Fragment } from 'react/jsx-runtime' +import { ClientOnly } from 'remix-utils/client-only' + +import Notice from '~/components/Notice' +import { getAcl, getContext, patchAcl } from '~/utils/config' +import { sighupHeadscale } from '~/utils/docker' +import { getSession } from '~/utils/sessions' + +import Editor from './editor' + +export async function loader() { + const context = await getContext() + if (!context.hasAcl) { + throw new Error('No ACL configuration is available') + } + + const acl = await getAcl() + return { + hasAclWrite: context.hasAclWrite, + currentAcl: acl + } +} + +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 context = await getContext() + if (!context.hasAclWrite) { + return json({ success: false }, { + status: 403 + }) + } + + const data = await request.json() as { acl: string } + await patchAcl(data.acl) + await sighupHeadscale() + return json({ success: true }) +} export default function Page() { + const data = useLoaderData() + const [acl, setAcl] = useState(data.currentAcl) + return ( -
- -

- Access Control Lists are currently unavailable. - They will be available in a future release. +

+ {data.hasAclWrite ? undefined : ( +
+ + The ACL policy file is readonly to Headplane. + You will not be able to make changes here. + +
+ )} + +

+ Access Control List (ACL) +

+ +

+ 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 documentation. + {' '} + + More information +

+ + + + + {({ selected }) => ( + + )} + + + {({ selected }) => ( + + )} + + + {({ selected }) => ( + + )} + + + + + + {() => ( + + )} + + + + + {() => ( + + )} + + + +
+ +

+ The Preview rules is very much still a work in progress. + It's a bit complicated to implement right now but hopefully it will be available soon. +

+
+
+
+
) } diff --git a/app/utils/config.ts b/app/utils/config.ts index 8ab7cb4..f1d67c4 100644 --- a/app/utils/config.ts +++ b/app/utils/config.ts @@ -131,6 +131,23 @@ export async function getConfig(force = false) { return config.toJSON() as Config } +export async function getAcl() { + let path = process.env.ACL_FILE + if (!path) { + try { + const config = await getConfig() + path = config.acl_policy_path + } catch {} + } + + if (!path) { + return '' + } + + const data = await readFile(path, 'utf8') + return data +} + // This is so obscenely dangerous, please have a check around it export async function patchConfig(partial: Record) { for (const [key, value] of Object.entries(partial)) { @@ -141,6 +158,22 @@ export async function patchConfig(partial: Record) { await writeFile(path, config.toString(), 'utf8') } +export async function patchAcl(data: string) { + let path = process.env.ACL_FILE + if (!path) { + try { + const config = await getConfig() + path = config.acl_policy_path + } catch {} + } + + if (!path) { + throw new Error('No ACL file defined') + } + + await writeFile(path, data, 'utf8') +} + let watcher: FSWatcher export function registerConfigWatcher() { diff --git a/app/utils/docker.ts b/app/utils/docker.ts index 422238f..af805f5 100644 --- a/app/utils/docker.ts +++ b/app/utils/docker.ts @@ -8,6 +8,31 @@ import { Client } from 'undici' import { getContext } from './config' import { pull } from './headscale' +export async function sighupHeadscale() { + const context = await getContext() + if (!context.hasDockerSock) { + return + } + + if (!process.env.HEADSCALE_CONTAINER) { + throw new Error('HEADSCALE_CONTAINER is not set') + } + + const client = new Client('http://localhost', { + socketPath: '/var/run/docker.sock' + }) + + const container = process.env.HEADSCALE_CONTAINER + const response = await client.request({ + method: 'POST', + path: `/v1.30/containers/${container}/kill?signal=SIGHUP` + }) + + if (!response.statusCode || response.statusCode !== 204) { + throw new Error('Failed to send SIGHUP to Headscale') + } +} + export async function restartHeadscale() { const context = await getContext() if (!context.hasDockerSock) { diff --git a/package.json b/package.json index f2b5eb0..67f7b33 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "typecheck": "tsc" }, "dependencies": { + "@codemirror/lang-json": "^6.0.1", "@dnd-kit/core": "^6.1.0", "@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/sortable": "^8.0.0", @@ -20,12 +21,16 @@ "@remix-run/node": "^2.8.1", "@remix-run/react": "^2.8.1", "@remix-run/serve": "^2.8.1", + "@uiw/codemirror-theme-github": "^4.21.25", + "@uiw/react-codemirror": "^4.21.25", "clsx": "^2.1.0", "isbot": "^4.1.0", "oauth4webapi": "^2.10.3", "react": "^18.2.0", + "react-codemirror-merge": "^4.21.25", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", + "remix-utils": "^7.6.0", "undici": "^6.10.2", "usehooks-ts": "^3.0.2", "yaml": "^2.4.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b744b87..537dec5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,9 @@ settings: excludeLinksFromLockfile: false dependencies: + '@codemirror/lang-json': + specifier: ^6.0.1 + version: 6.0.1 '@dnd-kit/core': specifier: ^6.1.0 version: 6.1.0(react-dom@18.2.0)(react@18.2.0) @@ -32,6 +35,12 @@ dependencies: '@remix-run/serve': specifier: ^2.8.1 version: 2.8.1(typescript@5.4.3) + '@uiw/codemirror-theme-github': + specifier: ^4.21.25 + version: 4.21.25(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3) + '@uiw/react-codemirror': + specifier: ^4.21.25 + version: 4.21.25(@babel/runtime@7.24.1)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.5.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0) clsx: specifier: ^2.1.0 version: 2.1.0 @@ -44,12 +53,18 @@ dependencies: react: specifier: ^18.2.0 version: 18.2.0 + react-codemirror-merge: + specifier: ^4.21.25 + version: 4.21.25(@babel/runtime@7.24.1)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.5.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0) react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) react-hot-toast: specifier: ^2.4.1 version: 2.4.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0) + remix-utils: + specifier: ^7.6.0 + version: 7.6.0(@remix-run/node@2.8.1)(@remix-run/react@2.8.1)(react@18.2.0) undici: specifier: ^6.10.2 version: 6.10.2 @@ -408,7 +423,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.1 - dev: true /@babel/template@7.24.0: resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} @@ -446,6 +460,94 @@ packages: to-fast-properties: 2.0.0 dev: true + /@codemirror/autocomplete@6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1): + resolution: {integrity: sha512-P/LeCTtZHRTCU4xQsa89vSKWecYv1ZqwzOd5topheGRf+qtacFgBeIMQi3eL8Kt/BUNvxUWkx+5qP2jlGoARrg==} + peerDependencies: + '@codemirror/language': ^6.0.0 + '@codemirror/state': ^6.0.0 + '@codemirror/view': ^6.0.0 + '@lezer/common': ^1.0.0 + dependencies: + '@codemirror/language': 6.10.1 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + '@lezer/common': 1.2.1 + dev: false + + /@codemirror/commands@6.3.3: + resolution: {integrity: sha512-dO4hcF0fGT9tu1Pj1D2PvGvxjeGkbC6RGcZw6Qs74TH+Ed1gw98jmUgd2axWvIZEqTeTuFrg1lEB1KV6cK9h1A==} + dependencies: + '@codemirror/language': 6.10.1 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + '@lezer/common': 1.2.1 + dev: false + + /@codemirror/lang-json@6.0.1: + resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==} + dependencies: + '@codemirror/language': 6.10.1 + '@lezer/json': 1.0.2 + dev: false + + /@codemirror/language@6.10.1: + resolution: {integrity: sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==} + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + '@lezer/common': 1.2.1 + '@lezer/highlight': 1.2.0 + '@lezer/lr': 1.4.0 + style-mod: 4.1.2 + dev: false + + /@codemirror/lint@6.5.0: + resolution: {integrity: sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==} + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + crelt: 1.0.6 + dev: false + + /@codemirror/merge@6.6.1: + resolution: {integrity: sha512-7wuc0R8+CSMlGZzEpxphQVkoBYb4D+M/MeB7/8g1ZrmLuP1wxhyOy7xWftmCzjKlVuRAUaKgBoA3LHS42H8eKA==} + dependencies: + '@codemirror/language': 6.10.1 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + '@lezer/highlight': 1.2.0 + style-mod: 4.1.2 + dev: false + + /@codemirror/search@6.5.6: + resolution: {integrity: sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==} + dependencies: + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + crelt: 1.0.6 + dev: false + + /@codemirror/state@6.4.1: + resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==} + dev: false + + /@codemirror/theme-one-dark@6.1.2: + resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==} + dependencies: + '@codemirror/language': 6.10.1 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + '@lezer/highlight': 1.2.0 + dev: false + + /@codemirror/view@6.26.3: + resolution: {integrity: sha512-gmqxkPALZjkgSxIeeweY/wGQXBfwTUaLs8h7OKtSwfbj9Ct3L11lD+u1sS7XHppxFQoMDiMDp07P9f3I2jWOHw==} + dependencies: + '@codemirror/state': 6.4.1 + style-mod: 4.1.2 + w3c-keyname: 2.2.8 + dev: false + /@dnd-kit/accessibility@3.1.0(react@18.2.0): resolution: {integrity: sha512-ea7IkhKvlJUv9iSHJOnxinBcoOI3ppGnnL+VDJ75O45Nss6HtZd8IdN8touXPDtASfeI2T2LImb8VOZcL47wjQ==} peerDependencies: @@ -1034,6 +1136,30 @@ packages: resolution: {integrity: sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw==} dev: true + /@lezer/common@1.2.1: + resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==} + dev: false + + /@lezer/highlight@1.2.0: + resolution: {integrity: sha512-WrS5Mw51sGrpqjlh3d4/fOwpEV2Hd3YOkp9DBt4k8XZQcoTHZFB7sx030A6OcahF4J1nDQAa3jXlTVVYH50IFA==} + dependencies: + '@lezer/common': 1.2.1 + dev: false + + /@lezer/json@1.0.2: + resolution: {integrity: sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==} + dependencies: + '@lezer/common': 1.2.1 + '@lezer/highlight': 1.2.0 + '@lezer/lr': 1.4.0 + dev: false + + /@lezer/lr@1.4.0: + resolution: {integrity: sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==} + dependencies: + '@lezer/common': 1.2.1 + dev: false + /@mdx-js/mdx@2.3.0: resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} dependencies: @@ -1689,6 +1815,75 @@ packages: eslint-visitor-keys: 3.4.3 dev: true + /@uiw/codemirror-extensions-basic-setup@4.21.25(@codemirror/autocomplete@6.16.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.1)(@codemirror/lint@6.5.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3): + resolution: {integrity: sha512-eeUKlmEE8aSoSgelS8OR2elcPGntpRo669XinAqPCLa0eKorT2B0d3ts+AE+njAeGk744tiyAEbHb2n+6OQmJw==} + peerDependencies: + '@codemirror/autocomplete': '>=6.0.0' + '@codemirror/commands': '>=6.0.0' + '@codemirror/language': '>=6.0.0' + '@codemirror/lint': '>=6.0.0' + '@codemirror/search': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + dependencies: + '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1) + '@codemirror/commands': 6.3.3 + '@codemirror/language': 6.10.1 + '@codemirror/lint': 6.5.0 + '@codemirror/search': 6.5.6 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + dev: false + + /@uiw/codemirror-theme-github@4.21.25(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3): + resolution: {integrity: sha512-RDS9s/Lbi1uIvupIXNiREFMryZjd7X4xRMKzmf6NfZuXWVdDATTA1b5smzxXldJgl8bY4QoOevczRncFTVRfGA==} + dependencies: + '@uiw/codemirror-themes': 4.21.25(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3) + transitivePeerDependencies: + - '@codemirror/language' + - '@codemirror/state' + - '@codemirror/view' + dev: false + + /@uiw/codemirror-themes@4.21.25(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3): + resolution: {integrity: sha512-C3t/voELxQj0eaVhrlgzaOnSALNf8bOcRbL5xN9r2+RkdsbFOmvNl3VVhlxEB7PSGc1jUZwVO4wQsB2AP178ag==} + peerDependencies: + '@codemirror/language': '>=6.0.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + dependencies: + '@codemirror/language': 6.10.1 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + dev: false + + /@uiw/react-codemirror@4.21.25(@babel/runtime@7.24.1)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.5.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-mBrCoiffQ+hbTqV1JoixFEcH7BHXkS3PjTyNH7dE8Gzf3GSBRazhtSM5HrAFIiQ5FIRGFs8Gznc4UAdhtevMmw==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@babel/runtime': 7.24.1 + '@codemirror/commands': 6.3.3 + '@codemirror/state': 6.4.1 + '@codemirror/theme-one-dark': 6.1.2 + '@codemirror/view': 6.26.3 + '@uiw/codemirror-extensions-basic-setup': 4.21.25(@codemirror/autocomplete@6.16.0)(@codemirror/commands@6.3.3)(@codemirror/language@6.10.1)(@codemirror/lint@6.5.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3) + codemirror: 6.0.1(@lezer/common@1.2.1) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + dev: false + /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true @@ -2232,6 +2427,20 @@ packages: engines: {node: '>=6'} dev: false + /codemirror@6.0.1(@lezer/common@1.2.1): + resolution: {integrity: sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==} + dependencies: + '@codemirror/autocomplete': 6.16.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.26.3)(@lezer/common@1.2.1) + '@codemirror/commands': 6.3.3 + '@codemirror/language': 6.10.1 + '@codemirror/lint': 6.5.0 + '@codemirror/search': 6.5.6 + '@codemirror/state': 6.4.1 + '@codemirror/view': 6.26.3 + transitivePeerDependencies: + - '@lezer/common' + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -2319,6 +2528,10 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true + /crelt@1.0.6: + resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -5311,6 +5524,33 @@ packages: iconv-lite: 0.4.24 unpipe: 1.0.0 + /react-codemirror-merge@4.21.25(@babel/runtime@7.24.1)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.5.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-k0OYi70B36O/059p5llx55E857Wam20aWwALymXmQr9YtC83X7OqKWj4/8iPpxB3aIK5H/smmMAjlky7u7ecMQ==} + peerDependencies: + '@babel/runtime': '>=7.11.0' + '@codemirror/state': '>=6.0.0' + '@codemirror/theme-one-dark': '>=6.0.0' + '@codemirror/view': '>=6.0.0' + codemirror: '>=6.0.0' + react: '>=16.8.0' + react-dom: '>=16.8.0' + dependencies: + '@babel/runtime': 7.24.1 + '@codemirror/merge': 6.6.1 + '@codemirror/state': 6.4.1 + '@codemirror/theme-one-dark': 6.1.2 + '@codemirror/view': 6.26.3 + '@uiw/react-codemirror': 4.21.25(@babel/runtime@7.24.1)(@codemirror/autocomplete@6.16.0)(@codemirror/language@6.10.1)(@codemirror/lint@6.5.0)(@codemirror/search@6.5.6)(@codemirror/state@6.4.1)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.26.3)(codemirror@6.0.1)(react-dom@18.2.0)(react@18.2.0) + codemirror: 6.0.1(@lezer/common@1.2.1) + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + transitivePeerDependencies: + - '@codemirror/autocomplete' + - '@codemirror/language' + - '@codemirror/lint' + - '@codemirror/search' + dev: false + /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -5441,7 +5681,6 @@ packages: /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - dev: true /regexp-tree@0.1.27: resolution: {integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==} @@ -5512,6 +5751,48 @@ packages: unified: 10.1.2 dev: true + /remix-utils@7.6.0(@remix-run/node@2.8.1)(@remix-run/react@2.8.1)(react@18.2.0): + resolution: {integrity: sha512-BPhCUEy+nwrhDDDg2v3+LFSszV6tluMbeSkbffj2o4tqZxt5Kn69Y9sNpGxYLAj8gjqeYDuxjv55of+gYnnykA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@remix-run/cloudflare': ^2.0.0 + '@remix-run/deno': ^2.0.0 + '@remix-run/node': ^2.0.0 + '@remix-run/react': ^2.0.0 + '@remix-run/router': ^1.7.2 + crypto-js: ^4.1.1 + intl-parse-accept-language: ^1.0.0 + is-ip: ^5.0.1 + react: ^18.0.0 + zod: ^3.22.4 + peerDependenciesMeta: + '@remix-run/cloudflare': + optional: true + '@remix-run/deno': + optional: true + '@remix-run/node': + optional: true + '@remix-run/react': + optional: true + '@remix-run/router': + optional: true + crypto-js: + optional: true + intl-parse-accept-language: + optional: true + is-ip: + optional: true + react: + optional: true + zod: + optional: true + dependencies: + '@remix-run/node': 2.8.1(typescript@5.4.3) + '@remix-run/react': 2.8.1(react-dom@18.2.0)(react@18.2.0)(typescript@5.4.3) + react: 18.2.0 + type-fest: 4.15.0 + dev: false + /require-like@0.1.2: resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} dev: true @@ -5941,6 +6222,10 @@ packages: engines: {node: '>=8'} dev: true + /style-mod@4.1.2: + resolution: {integrity: sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==} + dev: false + /style-to-object@0.4.4: resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} dependencies: @@ -6160,6 +6445,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest@4.15.0: + resolution: {integrity: sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==} + engines: {node: '>=16'} + dev: false + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -6485,6 +6775,10 @@ packages: fsevents: 2.3.3 dev: true + /w3c-keyname@2.2.8: + resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + dev: false + /wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: