From a9cb9510c53e12515b1854c6c07f8a8b179f75ab Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Mon, 1 Dec 2025 18:48:22 +0800 Subject: [PATCH] feat(frontend): request logs; refactor frontend components --- frontend/package-lock.json | 22 +++ frontend/package.json | 1 + frontend/src/App.tsx | 14 +- frontend/src/api/client.ts | 9 +- frontend/src/components/ChangePassword.tsx | 125 +++++++++--------- frontend/src/components/CommandQueue.tsx | 20 +-- frontend/src/components/Login.tsx | 16 ++- frontend/src/components/RequestDetails.tsx | 48 +++++++ frontend/src/components/RequestLog.tsx | 58 ++++++++ frontend/src/components/RulesList.tsx | 48 +++---- frontend/src/components/ui/Button.tsx | 41 ++++++ frontend/src/components/ui/Card.tsx | 59 +++++++++ frontend/src/components/ui/Input.tsx | 30 +++++ frontend/src/components/ui/Modal.tsx | 23 ++++ frontend/src/components/ui/Select.tsx | 30 +++++ frontend/src/components/ui/Textarea.tsx | 30 +++++ frontend/src/types/index.ts | 20 +++ migrations/20251130125600_init.sql | 15 +-- .../20251201181400_add_request_logs.sql | 14 ++ src/admin/handlers.rs | 21 ++- src/admin/models.rs | 18 +++ src/admin/routes.rs | 1 + src/db/mod.rs | 17 ++- src/db/models.rs | 23 +--- src/db/repositories/logs.rs | 39 ++++++ src/db/repositories/mod.rs | 1 + src/main.rs | 7 +- src/middleware.rs | 59 +++++---- 28 files changed, 649 insertions(+), 160 deletions(-) create mode 100644 frontend/src/components/RequestDetails.tsx create mode 100644 frontend/src/components/RequestLog.tsx create mode 100644 frontend/src/components/ui/Button.tsx create mode 100644 frontend/src/components/ui/Card.tsx create mode 100644 frontend/src/components/ui/Input.tsx create mode 100644 frontend/src/components/ui/Modal.tsx create mode 100644 frontend/src/components/ui/Select.tsx create mode 100644 frontend/src/components/ui/Textarea.tsx create mode 100644 migrations/20251201181400_add_request_logs.sql create mode 100644 src/db/repositories/logs.rs diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 18bef53..5c1a5e4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "mylinspirer-admin", "version": "0.1.0", "dependencies": { + "class-variance-authority": "^0.7.1", "solid-js": "^1.8.0" }, "devDependencies": { @@ -1423,6 +1424,27 @@ "node": ">= 6" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2d7971b..aedfb91 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,6 +9,7 @@ "preview": "vite preview" }, "dependencies": { + "class-variance-authority": "^0.7.1", "solid-js": "^1.8.0" }, "devDependencies": { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 12a3ac0..7445af5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,10 +3,11 @@ import RulesList from './components/RulesList'; import CommandQueue from './components/CommandQueue'; import Login from './components/Login'; import ChangePassword from './components/ChangePassword'; +import RequestLog from './components/RequestLog'; import { authStore } from './api/auth'; const App: Component = () => { - const [activeTab, setActiveTab] = createSignal<'rules' | 'commands'>('rules'); + const [activeTab, setActiveTab] = createSignal<'rules' | 'commands' | 'logs'>('rules'); const [isAuthenticated, setIsAuthenticated] = createSignal(false); const [showChangePassword, setShowChangePassword] = createSignal(false); @@ -73,6 +74,16 @@ const App: Component = () => { > Command Queue + @@ -80,6 +91,7 @@ const App: Component = () => {
{activeTab() === 'rules' && } {activeTab() === 'commands' && } + {activeTab() === 'logs' && }
diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index f6cbfc3..24463ce 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,4 +1,4 @@ -import type { InterceptionRule, Command, CreateRuleRequest, UpdateRuleRequest, UpdateCommandRequest } from '../types'; +import type { InterceptionRule, Command, CreateRuleRequest, UpdateRuleRequest, UpdateCommandRequest, RequestLog } from '../types'; import { authStore } from './auth'; const API_BASE = '/admin/api'; @@ -103,3 +103,10 @@ export const authApi = { }), }), }; + +// Logs API +export const logsApi = { + list: () => request('/logs'), +}; + +export const fetchLogs = logsApi.list; diff --git a/frontend/src/components/ChangePassword.tsx b/frontend/src/components/ChangePassword.tsx index 3f15101..9170606 100644 --- a/frontend/src/components/ChangePassword.tsx +++ b/frontend/src/components/ChangePassword.tsx @@ -1,6 +1,10 @@ import { Component, createSignal } from 'solid-js'; import { authApi } from '../api/client'; import { authStore } from '../api/auth'; +import { Button } from './ui/Button'; +import { Card, CardContent, CardFooter, CardHeader } from './ui/Card'; +import { Input } from './ui/Input'; +import { Modal, ModalContent } from './ui/Modal'; interface ChangePasswordProps { onClose: () => void; @@ -59,84 +63,81 @@ const ChangePassword: Component = (props) => { }; return ( -
-
-
+ + +

Change Password

-
+ -
- {error() && ( -
- {error()} + + + {error() && ( +
+ {error()} +
+ )} + + {success() && ( +
+ Password changed successfully! You will be logged out... +
+ )} + +
+ + setOldPassword(e.currentTarget.value)} + disabled={loading()} + />
- )} - {success() && ( -
- Password changed successfully! You will be logged out... +
+ + setNewPassword(e.currentTarget.value)} + disabled={loading()} + />
- )} -
- - setOldPassword(e.currentTarget.value)} - class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" - disabled={loading()} - /> -
- -
- - setNewPassword(e.currentTarget.value)} - class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" - disabled={loading()} - /> -
- -
- - setConfirmPassword(e.currentTarget.value)} - class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" - disabled={loading()} - /> -
- -
-
+ + + - -
+ + -
-
+ + ); }; diff --git a/frontend/src/components/CommandQueue.tsx b/frontend/src/components/CommandQueue.tsx index a64a23a..f21b73b 100644 --- a/frontend/src/components/CommandQueue.tsx +++ b/frontend/src/components/CommandQueue.tsx @@ -1,5 +1,7 @@ import { Component, createResource, For, Show } from 'solid-js'; import { commandsApi } from '../api/client'; +import { Button } from './ui/Button'; +import { Card } from './ui/Card'; const CommandQueue: Component = () => { const [commands, { refetch }] = createResource(commandsApi.list); @@ -22,7 +24,7 @@ const CommandQueue: Component = () => {

Command Queue

-
+ Loading...
}> @@ -56,18 +58,20 @@ const CommandQueue: Component = () => {
- - +
@@ -75,7 +79,7 @@ const CommandQueue: Component = () => { )}
- + ); }; diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx index 53ca327..2fb8654 100644 --- a/frontend/src/components/Login.tsx +++ b/frontend/src/components/Login.tsx @@ -1,4 +1,7 @@ import { Component, createSignal } from 'solid-js'; +import { Button } from './ui/Button'; +import { Card } from './ui/Card'; +import { Input } from './ui/Input'; interface LoginProps { onLoginSuccess: (token: string) => void; @@ -39,7 +42,7 @@ const Login: Component = (props) => { return (
-
+

My Linspirer Admin Login

@@ -49,12 +52,11 @@ const Login: Component = (props) => { - setPassword(e.currentTarget.value)} - class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Enter admin password" disabled={isLoading()} required @@ -67,19 +69,19 @@ const Login: Component = (props) => {
)} - +

Default password: admin123

-
+ ); }; diff --git a/frontend/src/components/RequestDetails.tsx b/frontend/src/components/RequestDetails.tsx new file mode 100644 index 0000000..540b5ac --- /dev/null +++ b/frontend/src/components/RequestDetails.tsx @@ -0,0 +1,48 @@ +import { Component, createSignal, For } from 'solid-js'; +import type { RequestLog } from '../types'; +import { Card } from './ui/Card'; + +const TreeView: Component<{ data: any; name: string }> = (props) => { + const [isOpen, setIsOpen] = createSignal(true); + const isObject = typeof props.data === 'object' && props.data !== null; + + return ( +
+
+ setIsOpen(!isOpen())}> + {isObject ? (isOpen() ? '▼' : '►') : ''} + + {props.name} +
+ {isOpen() && isObject && ( +
+ + {([key, value]) => { + if (typeof value === 'object' && value !== null && 'modified' in value && 'value' in value) { + return + } + return + }} + +
+ )} + {isOpen() && !isObject &&
{JSON.stringify(props.data)}
} +
+ ); +}; + + +const RequestDetails: Component<{ log: RequestLog }> = (props) => { + return ( + +

Request Details

+ {/* TODO: interception method */} +
+ + +
+
+ ); +}; + +export default RequestDetails; diff --git a/frontend/src/components/RequestLog.tsx b/frontend/src/components/RequestLog.tsx new file mode 100644 index 0000000..32f047d --- /dev/null +++ b/frontend/src/components/RequestLog.tsx @@ -0,0 +1,58 @@ +import { Component, createSignal, onMount, For, Show } from 'solid-js'; +import { fetchLogs } from '../api/client'; +import type { RequestLog as RequestLogType } from '../types'; +import RequestDetails from './RequestDetails'; +import { Card } from './ui/Card'; + +const RequestLog: Component = () => { + const [logs, setLogs] = createSignal([]); + const [selectedLog, setSelectedLog] = createSignal(null); + + onMount(async () => { + try { + const fetchedLogs = await fetchLogs(); + setLogs(fetchedLogs); + } catch (error) { + console.error('Error fetching logs:', error); + } + }); + + const handleLogClick = (log: RequestLogType) => { + setSelectedLog(log); + }; + + return ( +
+
+

Request Log

+
+ +
    + + {(log) => ( +
  • handleLogClick(log)} + > +
    + {log.method} + {new Date(log.created_at).toLocaleTimeString()} +
    +
  • + )} +
    +
+
+
+
+ + {(log) => +
+ +
} +
+
+ ); +}; + +export default RequestLog; diff --git a/frontend/src/components/RulesList.tsx b/frontend/src/components/RulesList.tsx index f576159..011045b 100644 --- a/frontend/src/components/RulesList.tsx +++ b/frontend/src/components/RulesList.tsx @@ -1,6 +1,11 @@ import { Component, createSignal, createResource, For, Show } from 'solid-js'; import { rulesApi } from '../api/client'; import type { InterceptionRule } from '../types'; +import { Button } from './ui/Button'; +import { Card } from './ui/Card'; +import { Input } from './ui/Input'; +import { Select } from './ui/Select'; +import { Textarea } from './ui/Textarea'; const RulesList: Component = () => { const [rules, { refetch }] = createResource(rulesApi.list); @@ -105,7 +110,7 @@ const RulesList: Component = () => {

Interception Rules

- +
-
+

{editingId() !== null ? 'Edit Rule' : 'Create New Rule'}

@@ -129,11 +133,10 @@ const RulesList: Component = () => { - setEditingMethod(e.currentTarget.value)} - class="w-full border border-gray-300 rounded-md px-3 py-2" placeholder="com.linspirer.method.name" />
@@ -142,14 +145,13 @@ const RulesList: Component = () => { - +
@@ -157,27 +159,25 @@ const RulesList: Component = () => { -