diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9e2b6b7
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+ "rust-analyzer.showUnlinkedFileNotification": false,
+ "cssVariables.lookupFiles": [
+ "**/*.css",
+ "**/*.scss",
+ "**/*.sass",
+ "**/*.less",
+ "node_modules/@mantine/core/styles.css"
+ ]
+}
diff --git a/license_ui/bun.lockb b/license_ui/bun.lockb
index 72e4ca7..5443269 100755
Binary files a/license_ui/bun.lockb and b/license_ui/bun.lockb differ
diff --git a/license_ui/package.json b/license_ui/package.json
index ad9341b..c521811 100644
--- a/license_ui/package.json
+++ b/license_ui/package.json
@@ -15,6 +15,7 @@
"@mantine/form": "^7.7.1",
"@mantine/hooks": "^7.7.1",
"@mantine/modals": "^7.7.1",
+ "@tabler/icons-react": "^3.1.0",
"clsx": "^2.0.0",
"dayjs": "^1.11.10",
"events": "^3.3.0",
diff --git a/license_ui/public/vite.svg b/license_ui/public/vite.svg
deleted file mode 100644
index e7b8dfb..0000000
--- a/license_ui/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/license_ui/src/App.css b/license_ui/src/App.css
deleted file mode 100644
index b9d355d..0000000
--- a/license_ui/src/App.css
+++ /dev/null
@@ -1,42 +0,0 @@
-#root {
- max-width: 1280px;
- margin: 0 auto;
- padding: 2rem;
- text-align: center;
-}
-
-.logo {
- height: 6em;
- padding: 1.5em;
- will-change: filter;
- transition: filter 300ms;
-}
-.logo:hover {
- filter: drop-shadow(0 0 2em #646cffaa);
-}
-.logo.react:hover {
- filter: drop-shadow(0 0 2em #61dafbaa);
-}
-
-@keyframes logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-@media (prefers-reduced-motion: no-preference) {
- a:nth-of-type(2) .logo {
- animation: logo-spin infinite 20s linear;
- }
-}
-
-.card {
- padding: 2em;
-}
-
-.read-the-docs {
- color: #888;
-}
diff --git a/license_ui/src/App.module.css b/license_ui/src/App.module.css
new file mode 100644
index 0000000..e4154db
--- /dev/null
+++ b/license_ui/src/App.module.css
@@ -0,0 +1,13 @@
+.container {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: stretch;
+ gap: var(--mantine-space-lg);
+}
+.form-column {
+ flex-grow: 1;
+}
+.product-column {
+ flex-grow: 5;
+}
diff --git a/license_ui/src/App.tsx b/license_ui/src/App.tsx
index afe48ac..a9f142d 100644
--- a/license_ui/src/App.tsx
+++ b/license_ui/src/App.tsx
@@ -1,35 +1,35 @@
-import { useState } from 'react'
-import reactLogo from './assets/react.svg'
-import viteLogo from '/vite.svg'
-import './App.css'
+import { Flex } from "@mantine/core";
+import classes from "./App.module.css";
+import { LicenseCode } from "./components/LicenseCode";
+import { LicenseForm } from "./components/LicenseForm";
+import { ProductList } from "./components/ProductList";
+import { Steps } from "./components/Steps";
function App() {
- const [count, setCount] = useState(0)
-
return (
- <>
-
- Vite + React
-
-
-
- Edit src/App.tsx
and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
- >
- )
+
+
+
+
+
+
+
+
+
+
+ );
}
-export default App
+export default App;
diff --git a/license_ui/src/assets/react.svg b/license_ui/src/assets/react.svg
deleted file mode 100644
index 6c87de9..0000000
--- a/license_ui/src/assets/react.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/license_ui/src/components/LicenseCode.module.css b/license_ui/src/components/LicenseCode.module.css
new file mode 100644
index 0000000..f8a43fd
--- /dev/null
+++ b/license_ui/src/components/LicenseCode.module.css
@@ -0,0 +1,6 @@
+.container {
+ flex-grow: 1;
+}
+.license-code-area {
+ flex-grow: 1;
+}
diff --git a/license_ui/src/components/LicenseCode.tsx b/license_ui/src/components/LicenseCode.tsx
new file mode 100644
index 0000000..23dd572
--- /dev/null
+++ b/license_ui/src/components/LicenseCode.tsx
@@ -0,0 +1,21 @@
+import { ActionIcon, Flex, Group, Paper, Textarea, Title, Tooltip } from "@mantine/core";
+import { IconCopy } from "@tabler/icons-react";
+import classes from "./LicenseCode.module.css";
+
+export function LicenseCode() {
+ return (
+
+
+
+ 授权码
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/license_ui/src/components/LicenseForm.tsx b/license_ui/src/components/LicenseForm.tsx
new file mode 100644
index 0000000..80d5435
--- /dev/null
+++ b/license_ui/src/components/LicenseForm.tsx
@@ -0,0 +1,63 @@
+import { Button, Flex, Group, Paper, Select, TextInput, Title } from "@mantine/core";
+import { IconAt, IconCalendar, IconTag, IconUser } from "@tabler/icons-react";
+import { pluck } from "ramda";
+
+const licenseValidLength = [
+ { value: 1, label: "一年" },
+ { value: 2, label: "两年" },
+ { value: 3, label: "三年" },
+ { value: 4, label: "四年" },
+ { value: 5, label: "五年" },
+ { value: 6, label: "六年" },
+ { value: 7, label: "七年" },
+ { value: 8, label: "八年" },
+ { value: 9, label: "九年" },
+ { value: 10, label: "十年" },
+ { value: 15, label: "十五年" },
+ { value: 20, label: "二十年" },
+ { value: 25, label: "二十五年" },
+ { value: 30, label: "三十年" },
+ { value: 35, label: "三十五年" },
+ { value: 40, label: "四十年" },
+ { value: 45, label: "四十五年" },
+ { value: 50, label: "五十年" },
+];
+
+export function LicenseForm() {
+ return (
+
+
+
+ );
+}
diff --git a/license_ui/src/components/ProductList.module.css b/license_ui/src/components/ProductList.module.css
new file mode 100644
index 0000000..3da53b8
--- /dev/null
+++ b/license_ui/src/components/ProductList.module.css
@@ -0,0 +1,3 @@
+.conatiner {
+ flex-grow: 1;
+}
diff --git a/license_ui/src/components/ProductList.tsx b/license_ui/src/components/ProductList.tsx
new file mode 100644
index 0000000..6440160
--- /dev/null
+++ b/license_ui/src/components/ProductList.tsx
@@ -0,0 +1,11 @@
+import { Flex, Paper, Title } from "@mantine/core";
+
+export function ProductList() {
+ return (
+
+
+ 产品列表
+
+
+ );
+}
diff --git a/license_ui/src/components/Steps.tsx b/license_ui/src/components/Steps.tsx
new file mode 100644
index 0000000..af9036c
--- /dev/null
+++ b/license_ui/src/components/Steps.tsx
@@ -0,0 +1,17 @@
+import { List, Paper } from "@mantine/core";
+
+export function Steps() {
+ return (
+
+
+
+ 首先下载netfilter.zip,解压缩到任意目录。
+
+
+ 按照其中README.pdf文件中的说明完成安装。
+
+ 之后你就可以在本页面上生成授权码了。
+
+
+ );
+}
diff --git a/license_ui/src/index.css b/license_ui/src/index.css
deleted file mode 100644
index 6119ad9..0000000
--- a/license_ui/src/index.css
+++ /dev/null
@@ -1,68 +0,0 @@
-:root {
- font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
-}
-
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
-
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
-
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
-}
-
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
diff --git a/license_ui/src/main.tsx b/license_ui/src/main.tsx
index 3d7150d..869baf7 100644
--- a/license_ui/src/main.tsx
+++ b/license_ui/src/main.tsx
@@ -1,10 +1,15 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import App from './App.tsx'
-import './index.css'
+import { MantineProvider } from "@mantine/core";
+import "@mantine/core/styles.css";
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App.tsx";
+import "./styles.css";
+import { theme } from "./theme";
-ReactDOM.createRoot(document.getElementById('root')!).render(
+ReactDOM.createRoot(document.getElementById("root")!).render(
-
- ,
-)
+
+
+
+
+);
diff --git a/license_ui/src/styles.css b/license_ui/src/styles.css
new file mode 100644
index 0000000..9f2a047
--- /dev/null
+++ b/license_ui/src/styles.css
@@ -0,0 +1,22 @@
+:root {
+ font-family: var(--mantine-font-family);
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+}
+
+body {
+ font-size: var(--font-size-m);
+ background-color: #202020;
+}
+
+body,
+#root {
+ @util size(100vw, 100vh);
+ padding: var(--mantine-spacing-md) var(--mantine-spacing-xl);
+ overflow: hidden;
+ user-select: none;
+}
diff --git a/license_ui/src/theme.ts b/license_ui/src/theme.ts
new file mode 100644
index 0000000..0f10ab9
--- /dev/null
+++ b/license_ui/src/theme.ts
@@ -0,0 +1,8 @@
+import { createTheme } from "@mantine/core";
+
+export const theme = createTheme({
+ focusRing: "never",
+ fontSmoothing: true,
+ defaultRadius: "xs",
+ lineHeights: "xs",
+});
diff --git a/license_ui/src/util.ts b/license_ui/src/util.ts
new file mode 100644
index 0000000..454b8f7
--- /dev/null
+++ b/license_ui/src/util.ts
@@ -0,0 +1,26 @@
+import cx, { ClassDictionary } from "clsx";
+import { defaultTo, isEmpty, isNil, prop } from "ramda";
+import { ChangeHandler } from "react-hook-form";
+
+export function convertFormEvent(
+ name: string,
+ event: InputEvent,
+ property: string = "value",
+ defaultValue?: unknown = null
+): Parameters {
+ return {
+ target: {
+ name,
+ value: defaultTo(defaultValue)(prop(property, event.currentTarget)),
+ },
+ type: event.type,
+ };
+}
+
+export function composite(classesDefination: ClassDictionary, ...classes: string[]) {
+ /** @type {import("clsx").ClassArray} */
+ const classesItems = classes
+ .filter((c) => !isNil(c) && !isEmpty(c))
+ .map((c) => prop(c, classesDefination) ?? c);
+ return cx(...classesItems);
+}
diff --git a/license_ui/vite.config.ts b/license_ui/vite.config.ts
index 861b04b..39f73b9 100644
--- a/license_ui/vite.config.ts
+++ b/license_ui/vite.config.ts
@@ -1,7 +1,18 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react-swc'
+import react from "@vitejs/plugin-react-swc";
+import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
-})
+ server: {
+ port: 3000,
+ proxy: {
+ "/api": {
+ target: `http://127.0.0.1:3000`,
+ changeOrigin: true,
+ secure: false,
+ rewrite: (path) => path.replace(/^\/api/, proxyHost.Path),
+ },
+ },
+ },
+});