project scaffolding + start on frontend

This commit is contained in:
2022-05-21 16:37:48 +02:00
parent 59ccc38ffd
commit 6addb48243
34 changed files with 2558 additions and 18 deletions

24
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

4
frontend/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"tabWidth": 2,
"useTabs": false
}

13
frontend/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Traefik Confman</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

28
frontend/package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "traefik-manager-frontend",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@babel/core": ">=7.0.0 <8.0.0",
"@heroicons/react": "^1.0.6",
"clsx": "^1.1.1",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "6"
},
"devDependencies": {
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@vitejs/plugin-react": "^1.3.0",
"autoprefixer": "^10.4.7",
"postcss": "^8.4.14",
"tailwindcss": "^3.0.24",
"typescript": "^4.6.3",
"vite": "^2.9.9"
}
}

1231
frontend/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

21
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,21 @@
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import MiddlewarePage from "./components/pages/middleware";
import RoutersPage from "./components/pages/routers";
import SecretsPage from "./components/pages/secrets";
import ServicesPage from "./components/pages/services";
import SetupPage from "./components/pages/setup";
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/services" element={<ServicesPage />} />
<Route path="/routers" element={<RoutersPage />} />
<Route path="/middleware" element={<MiddlewarePage />} />
<Route path="/setup" element={<SetupPage />} />
<Route path="/secrets" element={<SecretsPage />} />
<Route path="*" element={<Navigate to="/services" />} />
</Routes>
</BrowserRouter>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,11 @@
import { FC, HTMLProps } from "react";
const Badge: FC<HTMLProps<HTMLSpanElement>> = (props) => {
return (
<span className="px-2 py-1 bg-gray-300 text-gray-800 rounded-md text-xs">
{props.children}
</span>
);
};
export default Badge;

View File

@@ -0,0 +1,14 @@
import { FC, HTMLProps } from "react";
const Button: FC<HTMLProps<HTMLButtonElement>> = (props) => {
return (
<button
onClick={props.onClick}
className="flex items-center justify-center w-full px-10 py-4 text-base font-medium text-center text-white transition duration-500 ease-in-out transform bg-blue-600 rounded-xl hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
>
{props.children}
</button>
);
};
export default Button;

View File

@@ -0,0 +1,16 @@
import { FC, HTMLProps } from "react";
const Input: FC<HTMLProps<HTMLLabelElement>> = (props) => {
return (
<input
id={props.id}
name={props.name}
type={props.type}
placeholder={props.placeholder}
required={props.required}
className="block w-full px-5 py-3 text-base text-neutral-600 placeholder-gray-300 transition duration-500 ease-in-out transform border border-transparent rounded-lg bg-gray-50 focus:outline-none focus:border-transparent focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-300 shadow-sm"
/>
);
};
export default Input;

View File

@@ -0,0 +1,14 @@
import { FC, HTMLProps } from "react";
const InputLabel: FC<HTMLProps<HTMLLabelElement>> = (props) => {
return (
<label
htmlFor={props.id}
className="block text-sm font-medium text-gray-700"
>
{props.children}
</label>
);
};
export default InputLabel;

View File

@@ -0,0 +1,17 @@
import { FC, HTMLProps } from "react";
import Sidebar from "../molecules/Sidebar";
const DashboardLayout: FC<HTMLProps<HTMLDivElement>> = (props) => {
return (
<div className="flex h-screen overflow-hidden bg-white rounded-lg">
<Sidebar />
<div className="flex flex-col flex-1 w-0 overflow-hidden">
<main className="relative flex-1 overflow-y-auto focus:outline-none">
{props.children}
</main>
</div>
</div>
);
};
export default DashboardLayout;

View File

@@ -0,0 +1,130 @@
import {
CubeIcon,
GlobeAltIcon,
LockClosedIcon,
LogoutIcon,
PuzzleIcon,
} from "@heroicons/react/outline";
import { FC, ReactNode } from "react";
import { useNavigate } from "react-router-dom";
import Badge from "../atoms/Badge";
import InputLabel from "../atoms/InputLabel";
const SidebarTab: FC<{
onClick?: any;
children?: ReactNode;
badge?: ReactNode;
}> = (props) => {
return (
<button
onClick={props.onClick}
className="cursor-pointer inline-flex items-center justify-between w-full px-4 py-2 text-base text-black transition duration-500 ease-in-out transform rounded-lg focus:shadow-outline hover:bg-gray-100"
>
<div className="flex items-center">{props.children}</div>
{props.badge}
</button>
);
};
const Sidebar = () => {
const navigate = useNavigate();
const navFactory = (to: string) => () => {
navigate(to);
};
return (
<div className="hidden md:flex md:flex-shrink-0">
<div className="flex flex-col w-72">
<div className="flex flex-col flex-grow pt-5 overflow-y-auto bg-white border-r border-gray-200">
<div className="flex flex-col items-center flex-shrink-0 px-4 select-none">
<div className="flex flex-row justify-start items-center w-full ml-5 space-x-2">
<div className="w-12 h-12 bg-sky-300 rounded-md" />
<div>
<h1>Traefik</h1>
<h2 className="block text-xl font-extrabold tracking-tighter text-gray-900 transition duration-500 ease-in-out transform hover:text-gray-900">
Confman
</h2>
</div>
</div>
<button className="hidden rounded-lg focus:outline-none focus:shadow-outline">
<svg fill="currentColor" viewBox="0 0 20 20" className="w-6 h-6">
<path
fill-rule="evenodd"
d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM9 15a1 1 0 011-1h6a1 1 0 110 2h-6a1 1 0 01-1-1z"
clip-rule="evenodd"
></path>
<path
fill-rule="evenodd"
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
clip-rule="evenodd"
></path>
</svg>
</button>
</div>
<div className="flex flex-col flex-grow px-4 mt-8 space-y-2">
<div className="ml-3">
<InputLabel>Config</InputLabel>
</div>
<nav className="flex-1 space-y-1 bg-white">
<ul className="space-y-1">
<li>
<SidebarTab
badge={<Badge>0</Badge>}
onClick={navFactory("/services")}
>
<PuzzleIcon className="w-6 h-6 text-gray-700" />
<span className="ml-3">Services</span>
</SidebarTab>
</li>
<li>
<SidebarTab
badge={<Badge>0</Badge>}
onClick={navFactory("/routers")}
>
<GlobeAltIcon className="w-6 h-6 text-gray-700" />
<span className="ml-3">Routers</span>
</SidebarTab>
</li>
<li>
<SidebarTab
badge={<Badge>0</Badge>}
onClick={navFactory("/middleware")}
>
<CubeIcon className="w-6 h-6 text-gray-700" />
<span className="ml-3">Middleware</span>
</SidebarTab>
</li>
</ul>
</nav>
</div>
<div>
<div className="flex flex-col flex-grow px-4 mb-3 space-y-2">
<nav className="flex-1 space-y-1 bg-white">
<ul className="space-y-1">
<li>
<SidebarTab onClick={navFactory("/secrets")}>
<LockClosedIcon className="w-6 h-6 text-gray-700" />
<span className="ml-3">Secrets</span>
</SidebarTab>
</li>
</ul>
</nav>
</div>
<div className="flex flex-shrink-0 py-4 px-5 justify-between items-center border-t border-gray-200">
<div className="flex flex-row justify-center items-center space-x-4">
<p>nikuu</p>
</div>
<div className="w-6 h-6">
<LogoutIcon />
</div>
</div>
</div>
</div>
</div>
</div>
);
};
export default Sidebar;

View File

@@ -0,0 +1,21 @@
import { FC, HTMLProps } from "react";
import Input from "../atoms/Input";
import InputLabel from "../atoms/InputLabel";
const TextField: FC<HTMLProps<HTMLInputElement>> = (props) => {
return (
<div>
<InputLabel id={props.id}>{props.label}</InputLabel>
<div className="mt-1">
<Input
id={props.id}
type={props.type}
placeholder={props.placeholder}
required={props.required}
/>
</div>
</div>
);
};
export default TextField;

View File

@@ -0,0 +1,11 @@
import DashboardLayout from "../../layouts/Dashboard";
const MiddlewarePage = () => {
return (
<DashboardLayout>
<div>Middleware</div>
</DashboardLayout>
);
};
export default MiddlewarePage;

View File

@@ -0,0 +1,11 @@
import DashboardLayout from "../../layouts/Dashboard";
const RoutersPage = () => {
return (
<DashboardLayout>
<div>Routers</div>
</DashboardLayout>
);
};
export default RoutersPage;

View File

@@ -0,0 +1,11 @@
import DashboardLayout from "../../layouts/Dashboard";
const SecretsPage = () => {
return (
<DashboardLayout>
<div>Secrets</div>
</DashboardLayout>
);
};
export default SecretsPage;

View File

@@ -0,0 +1,11 @@
import DashboardLayout from "../../layouts/Dashboard";
const ServicesPage = () => {
return (
<DashboardLayout>
<div>Services</div>
</DashboardLayout>
);
};
export default ServicesPage;

View File

@@ -0,0 +1,62 @@
import { useNavigate } from "react-router-dom";
import Button from "../../atoms/Button";
import TextField from "../../molecules/TextField";
import TraefikImgUrl from "../../../assets/traefik.png";
const SetupPage = () => {
const navigator = useNavigate();
const handleContinue = () => navigator("/");
return (
<main>
<div className="flex flex-col justify-center min-h-screen py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<img className="w-40 h-40 mx-auto" src={TraefikImgUrl} />
<h2 className="mt-6 text-3xl font-extrabold text-center text-neutral-600">
Setup Traefik Confman
</h2>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="px-4 py-8 sm:px-10">
<form
className="space-y-6"
action="#"
method="POST"
data-bitwarden-watching="1"
>
<TextField
id="username"
label="Username"
placeholder="name"
required
/>
<TextField
id="password"
type="password"
label="Password"
placeholder="*****"
required
/>
<TextField
id="repeatPassword"
type="password"
label="Repeat Password"
placeholder="*****"
required
/>
<div>
<Button onClick={handleContinue}>Continue</Button>
</div>
</form>
</div>
</div>
</div>
</main>
);
};
export default SetupPage;

3
frontend/src/index.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

10
frontend/src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

1
frontend/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,13 @@
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {
colors: {
confman: {
"bg-primary": "#fafafa",
},
},
},
},
plugins: [],
};

21
frontend/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": ["vite.config.ts"]
}

7
frontend/vite.config.ts Normal file
View File

@@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()]
})