chore: initial project scaffold with admin web, backend, desktop client, and deployment setup

Add monorepo structure for NexaVPN WireGuard control plane including:
- .gitignore for node_modules, build artifacts, and environment files
- README with project overview, monorepo layout, and quick start guide
- Admin web UI with React, Vite, TypeScript, and nginx reverse proxy
- API client with type definitions for users, devices, policies, gateways, and audit logs
- Admin pages for dashboard, users, devices, policies, g
This commit is contained in:
2026-03-15 16:32:34 +01:00
commit 830491cb0d
91 changed files with 5279 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
import { PropsWithChildren } from "react";
export function Card({ children }: PropsWithChildren) {
return <div className="card">{children}</div>;
}

View File

@@ -0,0 +1,45 @@
import { NavLink, Outlet } from "react-router-dom";
const items = [
["Dashboard", "/"],
["Users", "/users"],
["Devices", "/devices"],
["Policies", "/policies"],
["Gateways", "/gateways"],
["Audit", "/audit"],
["Settings", "/settings"]
];
export function Layout() {
return (
<div className="shell">
<aside className="sidebar">
<div>
<p className="eyebrow">NexaVPN</p>
<h1>Control Plane</h1>
</div>
<nav className="nav">
{items.map(([label, path]) => (
<NavLink
key={path}
to={path}
className={({ isActive }) => (isActive ? "nav-link active" : "nav-link")}
>
{label}
</NavLink>
))}
</nav>
</aside>
<main className="content">
<header className="topbar">
<div>
<p className="eyebrow">Enterprise WireGuard</p>
<h2>Self-hosted VPN management</h2>
</div>
<div className="pill">Secure by design</div>
</header>
<Outlet />
</main>
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { PropsWithChildren, ReactNode } from "react";
type PageProps = PropsWithChildren<{
title: string;
subtitle: string;
actions?: ReactNode;
}>;
export function Page({ title, subtitle, actions, children }: PageProps) {
return (
<section className="page">
<div className="page-header">
<div>
<h3>{title}</h3>
<p>{subtitle}</p>
</div>
{actions}
</div>
{children}
</section>
);
}

View File

@@ -0,0 +1,37 @@
import { PropsWithChildren, ReactNode } from "react";
type Column = {
key: string;
label: string;
};
type TableProps<T> = {
columns: Column[];
rows: T[];
renderCell: (row: T, column: Column) => ReactNode;
};
export function Table<T>({ columns, rows, renderCell }: PropsWithChildren<TableProps<T>>) {
return (
<div className="table-wrap">
<table className="table">
<thead>
<tr>
{columns.map((column) => (
<th key={column.key}>{column.label}</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, index) => (
<tr key={index}>
{columns.map((column) => (
<td key={column.key}>{renderCell(row, column)}</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
}