diff options
Diffstat (limited to 'pages/wg2nd')
-rw-r--r-- | pages/wg2nd/desc.md | 13 | ||||
-rw-r--r-- | pages/wg2nd/index.tsx | 199 |
2 files changed, 212 insertions, 0 deletions
diff --git a/pages/wg2nd/desc.md b/pages/wg2nd/desc.md new file mode 100644 index 0000000..1d917ac --- /dev/null +++ b/pages/wg2nd/desc.md @@ -0,0 +1,13 @@ +wg2nd +===== + +`wg2nd` converts WireGuard configurations from `wg-quick` format into `systemd-networkd` compatible configurations. +This is a web port of `wg2nd` which converts WireGuard configurations into Bash commands to set up the `networkd` +device and network configurations. Note that the output is not compatible with Zsh. **The web port operates entirely on the client-side, +ensuring that no sensitive data is transmitted or stored externally.** For more comprehensive control and features, +including batch conversions and the option to install a firewall, the Linux tool `wg2nd` is available [here](https://www.git.flu0r1ne.net/wg2nd) +and can be installed from source. + +Both versions are open-source and dual-licensed under GPL-2.0 and MIT. Limitations and further details for each version +are elaborated in this [comprehensive post](/logs/announcing-wg2nd). For installation instructions +for the Linux tool and additional information, please refer to the [README](https://www.git.flu0r1ne.net/wg2nd/tree/README.md?h=main). diff --git a/pages/wg2nd/index.tsx b/pages/wg2nd/index.tsx new file mode 100644 index 0000000..02537b3 --- /dev/null +++ b/pages/wg2nd/index.tsx @@ -0,0 +1,199 @@ +// pages/index.tsx +import Head from 'next/head'; +import { useEffect, useRef, useState } from 'react'; + +import Box from '../../components/Box'; +import TextArea from '../../components/TextArea'; +import Input from '../../components/Input'; +import Button from '../../components/Button'; +import Markdown from '../../components/Markdown'; +import Typography from '../../components/Typography'; + +import Script from 'next/script'; + +// @ts-ignore +import desc from './desc.md'; + + +/* + * HOOK THE MODULE FROM THE DOM + */ + +declare global { + interface Window { + Module: any; + } +} + +const useModule = <T extends any>(onInitCallback? : (module: T) => void): T | null => { + const [module, setModule] = useState<T | null>(null); + + useEffect(() => { + + var Module = { + onRuntimeInitialized: function () { + if(onInitCallback !== undefined) { + onInitCallback(window.Module); + } + + setModule(window.Module); + }, + }; + + if(module === null && typeof window.Module === "object") { + if(onInitCallback !== undefined) { + onInitCallback(window.Module); + } + + setModule(window.Module); + } + + }); + + + return module; +}; + + +const DEMO_CONFIG = `[Interface] +PrivateKey = 0OCS+dV5wsDje6qUAEDQzPmTNWOLE9HE8kfGU1wJUE0= +Address = 10.55.127.342/32, ab00:aaaa:aaa:aa02::5:abcd/128 +DNS = 10.64.0.1 + +[Peer] +PublicKey = WBSnuq6Vswxz5G5zz9pUt60ZSA+JfZ1iTXdg0RJGjks= +AllowedIPs = 0.0.0.0/0,::0/0 +Endpoint = 128.45.210.64:51821 +`; + +let rows = 0; +for(const char of DEMO_CONFIG) + if(char == '\n') rows++; + +interface PanelProps { + children?: React.ReactNode; + customStyle?: React.CSSProperties; +}; + +const Panel: React.FC<PanelProps> = ({ children, customStyle }) => ( + <section style={{ + backgroundColor: '#f8f9fa', + marginTop: '1.5rem', + padding: '1.5rem', + ...customStyle + }}> + {children} + </section> +); + +interface Wg2ndModule { + wg2nd_cmdseq: (intfName : string, intfconfig : string) => string; +}; + +import { Breadcrumbs, LinkCrumb } from "../../components/Breadcrumbs"; + +const Wg2nd = () => { + const intfNameRef = useRef<HTMLInputElement>(null); + const intfConfigRef = useRef<HTMLTextAreaElement>(null); + const ndConfigRef = useRef<HTMLTextAreaElement>(null); + const parentDebounceDiv = useRef<HTMLDivElement>(null); + + const convertConfig = (module : Wg2ndModule | null) => { + if(intfNameRef.current === null || intfConfigRef.current === null || module === null) + return; + + const intfName = intfNameRef.current!.value; + const intfConfig = intfConfigRef.current!.value; + + console.log(module); + const result = module.wg2nd_cmdseq(intfName, intfConfig); + + // This is a hack: adjust a wrapper around the output text area + // Thus, we can detect the scrollHeight of the TextArea without + // the screen moving. + parentDebounceDiv.current!.style.height = ndConfigRef.current!.style.height; + ndConfigRef.current!.style.height = `0px`; + + ndConfigRef.current!.value = result; + + const scrollHeight = ndConfigRef.current!.scrollHeight; + ndConfigRef.current!.style.height = `${scrollHeight}px`; + parentDebounceDiv.current!.style.height = 'auto'; + }; + + const module = useModule<Wg2ndModule>(convertConfig); + + return ( + <> + <script src="/wg2nd/wg2nd_bindings.js" /> + <main + style={{ + maxWidth: '1200px', + margin: 'auto', + }} + > + {/* Description */} + <Panel> + <Breadcrumbs> + <LinkCrumb href="/">flu0r1ne.net</LinkCrumb> + <LinkCrumb href="/wg2nd">wg2nd</LinkCrumb> + </Breadcrumbs> + + <Markdown md={desc} /> + </Panel> + + {/* Input Panel */} + <Panel> + <Typography variant="h3">WireGuard Configuration</Typography> + <Box my={1}> + <label htmlFor="wg_config_intf_name"> + <Typography variant="h4" gutter>Interface Name</Typography> + </label> + <Input + ref={intfNameRef} + id="wg_config_intf_name" + defaultValue="wg0" + spellCheck={false} + /> + </Box> + <Box my={1}> + <label htmlFor="wg_config_conf"> + <Typography variant="h4" gutter>Interface Configuration</Typography> + </label> + <TextArea + ref={intfConfigRef} + id="wg_config_conf" + defaultValue={DEMO_CONFIG} + rows={rows} + spellCheck={false} + /> + </Box> + <Button + sx={{width: '100%'}} + onClick={(e) => { convertConfig(module); }} + >Generate</Button> + </Panel> + + {/* Output Panel */} + <Panel> + <label htmlFor="nd_config_cmds"> + <Typography variant="h3">Networkd Configuration</Typography> + </label> + <Box my={1}> + <div ref={parentDebounceDiv} > + <TextArea + sx={{ overflowY: 'hidden' }} + id="nd_config_cmds" + ref={ndConfigRef} + spellCheck={false} + readOnly + /> + </div> + </Box> + </Panel> + </main> + </> + ); +}; + +export default Wg2nd; |