project scaffolding + start on frontend
This commit is contained in:
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal 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
4
frontend/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal 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
28
frontend/package.json
Normal 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
1231
frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
21
frontend/src/App.tsx
Normal file
21
frontend/src/App.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
BIN
frontend/src/assets/traefik.png
Normal file
BIN
frontend/src/assets/traefik.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
11
frontend/src/components/atoms/Badge.tsx
Normal file
11
frontend/src/components/atoms/Badge.tsx
Normal 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;
|
||||
14
frontend/src/components/atoms/Button.tsx
Normal file
14
frontend/src/components/atoms/Button.tsx
Normal 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;
|
||||
16
frontend/src/components/atoms/Input.tsx
Normal file
16
frontend/src/components/atoms/Input.tsx
Normal 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;
|
||||
14
frontend/src/components/atoms/InputLabel.tsx
Normal file
14
frontend/src/components/atoms/InputLabel.tsx
Normal 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;
|
||||
17
frontend/src/components/layouts/Dashboard.tsx
Normal file
17
frontend/src/components/layouts/Dashboard.tsx
Normal 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;
|
||||
130
frontend/src/components/molecules/Sidebar.tsx
Normal file
130
frontend/src/components/molecules/Sidebar.tsx
Normal 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;
|
||||
21
frontend/src/components/molecules/TextField.tsx
Normal file
21
frontend/src/components/molecules/TextField.tsx
Normal 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;
|
||||
11
frontend/src/components/pages/middleware/index.tsx
Normal file
11
frontend/src/components/pages/middleware/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import DashboardLayout from "../../layouts/Dashboard";
|
||||
|
||||
const MiddlewarePage = () => {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div>Middleware</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default MiddlewarePage;
|
||||
11
frontend/src/components/pages/routers/index.tsx
Normal file
11
frontend/src/components/pages/routers/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import DashboardLayout from "../../layouts/Dashboard";
|
||||
|
||||
const RoutersPage = () => {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div>Routers</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoutersPage;
|
||||
11
frontend/src/components/pages/secrets/index.tsx
Normal file
11
frontend/src/components/pages/secrets/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import DashboardLayout from "../../layouts/Dashboard";
|
||||
|
||||
const SecretsPage = () => {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div>Secrets</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecretsPage;
|
||||
11
frontend/src/components/pages/services/index.tsx
Normal file
11
frontend/src/components/pages/services/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import DashboardLayout from "../../layouts/Dashboard";
|
||||
|
||||
const ServicesPage = () => {
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div>Services</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServicesPage;
|
||||
62
frontend/src/components/pages/setup/index.tsx
Normal file
62
frontend/src/components/pages/setup/index.tsx
Normal 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
3
frontend/src/index.css
Normal file
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
10
frontend/src/main.tsx
Normal file
10
frontend/src/main.tsx
Normal 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
1
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
13
frontend/tailwind.config.js
Normal file
13
frontend/tailwind.config.js
Normal 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
21
frontend/tsconfig.json
Normal 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" }]
|
||||
}
|
||||
8
frontend/tsconfig.node.json
Normal file
8
frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
frontend/vite.config.ts
Normal file
7
frontend/vite.config.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()]
|
||||
})
|
||||
Reference in New Issue
Block a user