mirror of
https://github.com/MetaCubeX/metacubexd.git
synced 2024-11-23 09:25:35 +08:00
chore: initial commit
This commit is contained in:
commit
b8c6c2fa7a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"],
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
34
README.md
Normal file
34
README.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
## Usage
|
||||||
|
|
||||||
|
Those templates dependencies are maintained via [pnpm](https://pnpm.io) via `pnpm up -Lri`.
|
||||||
|
|
||||||
|
This is the reason you see a `pnpm-lock.yaml`. That being said, any package manager will work. This file can be safely be removed once you clone a template.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install # or pnpm install or yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm run dev` or `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.<br>
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||||
|
|
||||||
|
The page will reload if you make edits.<br>
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `dist` folder.<br>
|
||||||
|
It correctly bundles Solid in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.<br>
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
You can deploy the `dist` folder to any static host provider (netlify, surge, now, etc.)
|
16
index.html
Normal file
16
index.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<link rel="shortcut icon" type="image/svg" href="/src/assets/favicon.svg" />
|
||||||
|
<title>Solid App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="/src/index.tsx" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
46
package.json
Normal file
46
package.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "vite-template-solid",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"dev": "vite",
|
||||||
|
"serve": "vite preview",
|
||||||
|
"start": "vite"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@felte/solid": "^1.2.10",
|
||||||
|
"@felte/validator-zod": "^1.0.16",
|
||||||
|
"@solid-primitives/event-listener": "^2.2.14",
|
||||||
|
"@solid-primitives/storage": "^2.1.1",
|
||||||
|
"@solid-primitives/websocket": "^1.1.0",
|
||||||
|
"@solidjs/router": "^0.8.3",
|
||||||
|
"@tabler/icons-solidjs": "^2.32.0",
|
||||||
|
"@tanstack/solid-table": "^8.9.3",
|
||||||
|
"@tanstack/solid-virtual": "3.0.0-beta.6",
|
||||||
|
"@types/byte-size": "^8.1.0",
|
||||||
|
"@types/node": "^20.5.6",
|
||||||
|
"@types/uuid": "^9.0.2",
|
||||||
|
"apexcharts": "^3.42.0",
|
||||||
|
"autoprefixer": "^10.4.15",
|
||||||
|
"byte-size": "^8.1.1",
|
||||||
|
"daisyui": "^3.6.3",
|
||||||
|
"is-ip": "^5.0.1",
|
||||||
|
"ky": "^0.33.3",
|
||||||
|
"prettier": "^3.0.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.3",
|
||||||
|
"solid-apexcharts": "^0.3.2",
|
||||||
|
"solid-devtools": "^0.27.7",
|
||||||
|
"solid-form-handler": "^1.2.0",
|
||||||
|
"solid-js": "^1.7.11",
|
||||||
|
"tailwind-merge": "^1.14.0",
|
||||||
|
"tailwindcss": "^3.3.3",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"uuid": "^9.0.0",
|
||||||
|
"vite": "^4.4.9",
|
||||||
|
"vite-plugin-solid": "^2.7.0",
|
||||||
|
"zod": "^3.22.2"
|
||||||
|
}
|
||||||
|
}
|
2131
pnpm-lock.yaml
Normal file
2131
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
6
postcss.config.js
Normal file
6
postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
71
src/App.tsx
Normal file
71
src/App.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Route, Routes, useLocation, useNavigate } from '@solidjs/router'
|
||||||
|
import { For, Show, onMount } from 'solid-js'
|
||||||
|
import { Header } from '~/components/Header'
|
||||||
|
import { themes } from '~/constants'
|
||||||
|
import { Config } from '~/pages/Config'
|
||||||
|
import { Connections } from '~/pages/Connections'
|
||||||
|
import { Logs } from '~/pages/Logs'
|
||||||
|
import { Overview } from '~/pages/Overview'
|
||||||
|
import { Proxies } from '~/pages/Proxies'
|
||||||
|
import { Rules } from '~/pages/Rules'
|
||||||
|
import { Setup } from '~/pages/Setup'
|
||||||
|
import { curTheme, selectedEndpoint, setCurTheme } from '~/signals'
|
||||||
|
|
||||||
|
export const App = () => {
|
||||||
|
const location = useLocation()
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
if (!selectedEndpoint()) {
|
||||||
|
navigate('/setup')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="app relative" data-theme={curTheme()}>
|
||||||
|
<div class="sticky inset-x-0 top-0 z-50 flex items-center rounded-md bg-base-200 px-4 py-2">
|
||||||
|
<Show when={location.pathname !== '/setup'}>
|
||||||
|
<Header />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<div class="dropdown-end dropdown-hover dropdown ml-auto">
|
||||||
|
<label tabindex="0" class="btn btn-sm m-2 uppercase">
|
||||||
|
Themes
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
tabindex="0"
|
||||||
|
class="menu dropdown-content rounded-box z-[1] gap-2 bg-base-300 p-2 shadow"
|
||||||
|
>
|
||||||
|
<For each={themes}>
|
||||||
|
{(theme) => (
|
||||||
|
<li
|
||||||
|
data-theme={theme}
|
||||||
|
class="btn btn-xs"
|
||||||
|
onClick={() => setCurTheme(theme)}
|
||||||
|
>
|
||||||
|
{theme}
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="py-4">
|
||||||
|
<Routes>
|
||||||
|
<Show when={selectedEndpoint()}>
|
||||||
|
<Route path="/" component={Overview} />
|
||||||
|
<Route path="/proxies" component={Proxies} />
|
||||||
|
<Route path="/rules" component={Rules} />
|
||||||
|
<Route path="/conns" component={Connections} />
|
||||||
|
<Route path="/logs" component={Logs} />
|
||||||
|
<Route path="/config" component={Config} />
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Route path="/setup" component={Setup} />
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
51
src/assets/favicon.svg
Normal file
51
src/assets/favicon.svg
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 256 128" style="enable-background:new 0 0 256 128;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#333333;fill-opacity:0;}
|
||||||
|
.st1{opacity:0.8;fill:#E62C5A;enable-background:new;}
|
||||||
|
.st2{opacity:0.8;fill:#0FABF6;enable-background:new;}
|
||||||
|
.st3{opacity:0.5;fill:#501B8D;enable-background:new;}
|
||||||
|
.st4{fill:#333333;}
|
||||||
|
</style>
|
||||||
|
<g transform="translate(81.56239318847656,79.98116683959961)">
|
||||||
|
<g transform="translate(58.43760681152344,0)">
|
||||||
|
<g>
|
||||||
|
<rect x="-51.3" y="-72.1" class="st0" width="78.6" height="85.2"/>
|
||||||
|
<path class="st1" d="M14-24.9l-30.9-30.7l-15.6-15.5c-1.6-0.7-3.2-1-4.9-1c-7.5,0-13.6,6.9-13.8,15.5v37
|
||||||
|
c-0.1,8.8,6.9,16,15.7,16.1c0,0,0,0,0,0H7.7c5.2-2.5,8.5-7.8,8.5-13.6C16.2-19.8,15.4-22.6,14-24.9z"/>
|
||||||
|
<path class="st2" d="M11.6-55.6h-43.3c-5.2,2.5-8.5,7.8-8.5,13.6c0,2.8,0.8,5.5,2.2,7.8L-7.1-3.5L8.6,12.1c1.6,0.7,3.2,1,4.9,1
|
||||||
|
c7.5,0,13.6-6.9,13.8-15.5v-37.1C27.4-48.2,20.4-55.4,11.6-55.6z"/>
|
||||||
|
<path class="st3" d="M-38-34.1L-7.1-3.5H7.7c5.2-2.5,8.5-7.8,8.5-13.6c0-2.8-0.8-5.5-2.2-7.8l-30.9-30.7h-14.8
|
||||||
|
c-5.2,2.5-8.5,7.8-8.5,13.6C-40.2-39.2-39.4-36.5-38-34.1z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(0,72.08086395263672)">
|
||||||
|
<g>
|
||||||
|
<g transform="scale(0.35999999999999927)">
|
||||||
|
<g>
|
||||||
|
<path class="st4" d="M-23.8-110l-12.6-19.6c-1-1.9-3.5-1.3-3.5,0.9V-99c0,2.5,3.8,2.5,3.8,0v-23.4l10.5,16.4
|
||||||
|
c0.8,1.5,2.7,1.5,3.6,0l10.5-16.5V-99c0,2.5,3.8,2.5,3.8,0v-29.7c0-2.2-2.4-2.8-3.6-0.9L-23.8-110z M5.1-112.2h19.4
|
||||||
|
c2.2,0,2.1-3.3,0-3.3H5.1v-11.6h20.4c2.2,0,2.3-3.3,0-3.3H3.7c-1.4,0-2.4,1-2.4,2.2v28.3c0,1.2,0.9,2.3,2.4,2.3h21.7
|
||||||
|
c2.5,0,2.2-3.4,0-3.4H5.1V-112.2z M32.7-130.4c-2.4,0-2.4,3.6,0,3.6h11.4v28c0,1.3,1,1.9,1.9,1.9c0.9,0,2-0.6,2-1.9
|
||||||
|
c0-9.3-0.1-18.7-0.1-28h11.5c2.5,0,2.5-3.6,0-3.6H32.7z M82.5-109.5H66.3l8.1-16.2L82.5-109.5z M84.1-106.2l4,8
|
||||||
|
c1,2,4.8,0.7,3.5-1.5l-15.5-30.5c-0.4-0.7-1-1-1.7-1c-0.7,0-1.4,0.3-1.7,1L57.1-99.6c-0.9,2.1,2.5,3.4,3.4,1.5l4.2-8.1H84.1z
|
||||||
|
M124-103.5c1.3-1.9-1.8-3.8-3-2.1c-1.1,1.4-2.6,2.6-4.2,3.5c-2,1-4.1,1.5-6,1.5c-7.1,0-13.1-6-13.1-13.8
|
||||||
|
c0-7.5,4.5-12,10.8-12.9c4.5-0.5,8.6,0,12.1,3.8c1.4,1.5,4.2-0.6,2.7-2.3c-4.1-4.3-9.3-5.8-15.4-5c-7.9,1.2-14,7.9-14,16.4
|
||||||
|
c0,10.2,7.9,17.4,16.8,17.4c2.5,0,5.3-0.7,7.7-1.9C120.6-99.9,122.6-101.5,124-103.5z M156.7-112.1c0,14.9-22.7,14.9-22.7,0
|
||||||
|
V-129c0-2.3-3.9-2.3-3.9,0v17c0,9.9,7.7,14.9,15.2,14.9c7.5,0,15.2-5,15.2-14.9v-17c0-2.5-3.9-2.5-3.9,0V-112.1z M198.1-107.3
|
||||||
|
c0-4.2-2.6-6.8-6.3-8c2.7-1.1,4-3.5,4-6.1c0-7.3-7.4-8.9-12.2-8.9h-12.2c-1,0-1.9,0.8-1.9,1.9v29.1c0,1,0.9,1.9,1.9,1.9h13.4
|
||||||
|
C191.2-97.6,198.1-99.6,198.1-107.3z M183.5-127c3.2,0,8.5,1.1,8.5,5.6c0,3.8-3.9,5-8.3,5h-10.5V-127H183.5z M173.2-113.1h13.1
|
||||||
|
c4.5,0,8,1.8,8,5.8c0,5.2-5,6.4-9.5,6.4h-11.6V-113.1z M208.5-112.2h19.4c2.2,0,2.1-3.3,0-3.3h-19.4v-11.6h20.4
|
||||||
|
c2.2,0,2.3-3.3,0-3.3h-21.8c-1.4,0-2.4,1-2.4,2.2v28.3c0,1.2,0.9,2.3,2.4,2.3h21.7c2.5,0,2.2-3.4,0-3.4h-20.4V-112.2z
|
||||||
|
M238.4-130.3c-1.5-1.8-4.3,0.5-2.8,2.2l11.9,13.7l-12.3,14c-1.7,2,0.9,4.4,2.8,2.3l11.9-13.7L261.7-98c2,2,4.6-0.1,2.9-2.3
|
||||||
|
l-12.3-14.1l12-13.6c1.6-1.8-1.3-4.1-2.8-2.3l-11.4,13.4L238.4-130.3z M272.6-127.1h9.9c7.7,0,11.6,6.6,11.6,13.1
|
||||||
|
c0,6.5-3.9,13-11.6,13h-9.9V-127.1z M282.5-97.6c10.3,0,15.4-8.2,15.4-16.4c0-8.2-5.1-16.4-15.4-16.4h-11.2
|
||||||
|
c-1.3,0-2.4,1-2.4,2.2v28.4c0,1.2,1.1,2.2,2.4,2.2H282.5z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 3.4 KiB |
69
src/components/Header.tsx
Normal file
69
src/components/Header.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { A, useNavigate } from '@solidjs/router'
|
||||||
|
import {
|
||||||
|
IconFileStack,
|
||||||
|
IconGlobe,
|
||||||
|
IconHome,
|
||||||
|
IconNetwork,
|
||||||
|
IconNetworkOff,
|
||||||
|
IconRuler,
|
||||||
|
IconSettings,
|
||||||
|
} from '@tabler/icons-solidjs'
|
||||||
|
import { setSelectedEndpoint } from '~/signals'
|
||||||
|
|
||||||
|
export const Header = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul class="menu rounded-box menu-horizontal">
|
||||||
|
<li>
|
||||||
|
<A class="tooltip tooltip-bottom" href="/" data-tip="Home">
|
||||||
|
<IconHome />
|
||||||
|
</A>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<A class="tooltip tooltip-bottom" href="/proxies" data-tip="Proxies">
|
||||||
|
<IconGlobe />
|
||||||
|
</A>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<A class="tooltip tooltip-bottom" href="/rules" data-tip="Rules">
|
||||||
|
<IconRuler />
|
||||||
|
</A>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<A class="tooltip tooltip-bottom" href="/conns" data-tip="Connections">
|
||||||
|
<IconNetwork />
|
||||||
|
</A>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<A class="tooltip tooltip-bottom" href="/logs" data-tip="Logs">
|
||||||
|
<IconFileStack />
|
||||||
|
</A>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<A class="tooltip tooltip-bottom" href="/config" data-tip="Config">
|
||||||
|
<IconSettings />
|
||||||
|
</A>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<button
|
||||||
|
class="tooltip tooltip-bottom"
|
||||||
|
data-tip="Switch Endpoint"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedEndpoint('')
|
||||||
|
|
||||||
|
navigate('/setup')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconNetworkOff />
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
31
src/constants.ts
Normal file
31
src/constants.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export const themes = [
|
||||||
|
'light',
|
||||||
|
'dark',
|
||||||
|
'cupcake',
|
||||||
|
'bumblebee',
|
||||||
|
'emerald',
|
||||||
|
'corporate',
|
||||||
|
'synthwave',
|
||||||
|
'retro',
|
||||||
|
'cyberpunk',
|
||||||
|
'valentine',
|
||||||
|
'halloween',
|
||||||
|
'garden',
|
||||||
|
'forest',
|
||||||
|
'aqua',
|
||||||
|
'lofi',
|
||||||
|
'pastel',
|
||||||
|
'fantasy',
|
||||||
|
'wireframe',
|
||||||
|
'black',
|
||||||
|
'luxury',
|
||||||
|
'dracula',
|
||||||
|
'cmyk',
|
||||||
|
'autumn',
|
||||||
|
'business',
|
||||||
|
'acid',
|
||||||
|
'lemonade',
|
||||||
|
'night',
|
||||||
|
'coffee',
|
||||||
|
'winter',
|
||||||
|
]
|
10
src/index.css
Normal file
10
src/index.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
@tailwind variants;
|
||||||
|
|
||||||
|
.app {
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 1rem;
|
||||||
|
@apply subpixel-antialiased;
|
||||||
|
}
|
18
src/index.tsx
Normal file
18
src/index.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
/* @refresh reload */
|
||||||
|
import 'solid-devtools'
|
||||||
|
import { render } from 'solid-js/web'
|
||||||
|
|
||||||
|
import { Router } from '@solidjs/router'
|
||||||
|
import { App } from './App'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
const root = document.getElementById('root')
|
||||||
|
|
||||||
|
render(
|
||||||
|
() => (
|
||||||
|
<Router>
|
||||||
|
<App />
|
||||||
|
</Router>
|
||||||
|
),
|
||||||
|
root!,
|
||||||
|
)
|
3
src/pages/Config.tsx
Normal file
3
src/pages/Config.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const Config = () => {
|
||||||
|
return <div>config</div>
|
||||||
|
}
|
162
src/pages/Connections.tsx
Normal file
162
src/pages/Connections.tsx
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
import { createEventSignal } from '@solid-primitives/event-listener'
|
||||||
|
import { createReconnectingWS } from '@solid-primitives/websocket'
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
createSolidTable,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
} from '@tanstack/solid-table'
|
||||||
|
import byteSize from 'byte-size'
|
||||||
|
import { isIPv6 } from 'is-ip'
|
||||||
|
import { For, createSignal } from 'solid-js'
|
||||||
|
import { wsEndpointURL } from '~/signals'
|
||||||
|
import type { Connection } from '../types'
|
||||||
|
|
||||||
|
export const Connections = () => {
|
||||||
|
const [search, setSearch] = createSignal('')
|
||||||
|
|
||||||
|
const ws = createReconnectingWS(`${wsEndpointURL()}/connections`)
|
||||||
|
|
||||||
|
const messageEvent = createEventSignal<{
|
||||||
|
message: WebSocketEventMap['message']
|
||||||
|
}>(ws, 'message')
|
||||||
|
|
||||||
|
const connections = () => {
|
||||||
|
const data = messageEvent()?.data
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
JSON.parse(data) as { connections: Connection[] }
|
||||||
|
).connections.slice(-100)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnDef<Connection>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'ID',
|
||||||
|
accessorFn: (row) => row.id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Network',
|
||||||
|
accessorFn: (row) => row.metadata.network,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Download',
|
||||||
|
accessorFn: (row) => byteSize(row.download),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Upload',
|
||||||
|
accessorFn: (row) => byteSize(row.upload),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Rule',
|
||||||
|
accessorFn: (row) => row.rule,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Chains',
|
||||||
|
accessorFn: (row) => row.chains.join(' -> '),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Remote Destination',
|
||||||
|
accessorFn: (row) => row.metadata.remoteDestination,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Host',
|
||||||
|
accessorFn: (row) => row.metadata.host,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'DNS Mode',
|
||||||
|
accessorFn: (row) => row.metadata.dnsMode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Type',
|
||||||
|
accessorFn: (row) => row.metadata.type,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Source',
|
||||||
|
accessorFn: (row) =>
|
||||||
|
isIPv6(row.metadata.sourceIP)
|
||||||
|
? `[${row.metadata.sourceIP}]:${row.metadata.sourcePort}`
|
||||||
|
: `${row.metadata.sourceIP}:${row.metadata.sourcePort}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'Destination',
|
||||||
|
accessorFn: (row) =>
|
||||||
|
isIPv6(row.metadata.destinationIP)
|
||||||
|
? `[${row.metadata.destinationIP}]:${row.metadata.destinationPort}`
|
||||||
|
: `${row.metadata.destinationIP}:${row.metadata.destinationPort}`,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const table = createSolidTable({
|
||||||
|
get data() {
|
||||||
|
return search()
|
||||||
|
? connections().filter((connection) =>
|
||||||
|
Object.values(connection).some((conn) =>
|
||||||
|
JSON.stringify(conn)
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(search().toLowerCase()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: connections()
|
||||||
|
},
|
||||||
|
columns,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<input
|
||||||
|
class="input input-primary"
|
||||||
|
placeholder="Search"
|
||||||
|
onInput={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto whitespace-nowrap">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<For each={table.getHeaderGroups()}>
|
||||||
|
{(headerGroup) => (
|
||||||
|
<tr>
|
||||||
|
<For each={headerGroup.headers}>
|
||||||
|
{(header) => (
|
||||||
|
<th>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<For each={table.getRowModel().rows}>
|
||||||
|
{(row) => (
|
||||||
|
<tr>
|
||||||
|
<For each={row.getVisibleCells()}>
|
||||||
|
{(cell) => (
|
||||||
|
<td>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext(),
|
||||||
|
)}
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
58
src/pages/Logs.tsx
Normal file
58
src/pages/Logs.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { createEventSignal } from '@solid-primitives/event-listener'
|
||||||
|
import { createReconnectingWS } from '@solid-primitives/websocket'
|
||||||
|
import { For, createEffect, createSignal } from 'solid-js'
|
||||||
|
import { wsEndpointURL } from '~/signals'
|
||||||
|
|
||||||
|
export const Logs = () => {
|
||||||
|
const [search, setSearch] = createSignal('')
|
||||||
|
const [logs, setLogs] = createSignal<string[]>([])
|
||||||
|
|
||||||
|
const ws = createReconnectingWS(`${wsEndpointURL()}/logs`)
|
||||||
|
|
||||||
|
const messageEvent = createEventSignal<{
|
||||||
|
message: WebSocketEventMap['message']
|
||||||
|
}>(ws, 'message')
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const data = messageEvent()?.data
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setLogs((logs) =>
|
||||||
|
[
|
||||||
|
...logs,
|
||||||
|
(JSON.parse(data) as { type: string; payload: string }).payload,
|
||||||
|
].slice(-100),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<input
|
||||||
|
class="input input-primary"
|
||||||
|
placeholder="Search"
|
||||||
|
onInput={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto whitespace-nowrap">
|
||||||
|
<For
|
||||||
|
each={
|
||||||
|
search()
|
||||||
|
? logs().filter((log) =>
|
||||||
|
log.toLowerCase().includes(search().toLowerCase()),
|
||||||
|
)
|
||||||
|
: logs()
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(log) => (
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div class="flex-shrink-0">{log}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
181
src/pages/Overview.tsx
Normal file
181
src/pages/Overview.tsx
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import { createEventSignal } from '@solid-primitives/event-listener'
|
||||||
|
import { createReconnectingWS } from '@solid-primitives/websocket'
|
||||||
|
import { ApexOptions } from 'apexcharts'
|
||||||
|
import byteSize from 'byte-size'
|
||||||
|
import { SolidApexCharts } from 'solid-apexcharts'
|
||||||
|
import { createEffect, createMemo, createSignal } from 'solid-js'
|
||||||
|
import { wsEndpointURL } from '~/signals'
|
||||||
|
import type { Connection } from '~/types'
|
||||||
|
|
||||||
|
const defaultChartOptions: ApexOptions = {
|
||||||
|
chart: {
|
||||||
|
toolbar: { show: false },
|
||||||
|
zoom: { enabled: false },
|
||||||
|
animations: { easing: 'linear', dynamicAnimation: { speed: 1000 } },
|
||||||
|
},
|
||||||
|
noData: { text: 'Loading...' },
|
||||||
|
legend: {
|
||||||
|
fontSize: '14px',
|
||||||
|
labels: { colors: 'gray' },
|
||||||
|
itemMargin: { horizontal: 64 },
|
||||||
|
},
|
||||||
|
dataLabels: { enabled: false },
|
||||||
|
grid: { yaxis: { lines: { show: false } } },
|
||||||
|
stroke: { curve: 'smooth' },
|
||||||
|
tooltip: { enabled: false },
|
||||||
|
xaxis: { labels: { show: false }, axisTicks: { show: false } },
|
||||||
|
yaxis: {
|
||||||
|
labels: {
|
||||||
|
style: { colors: 'gray' },
|
||||||
|
formatter(val) {
|
||||||
|
return byteSize(val).toString()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Overview = () => {
|
||||||
|
const [traffics, setTraffics] = createSignal<{ down: number; up: number }[]>(
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
const [memories, setMemories] = createSignal<number[]>([])
|
||||||
|
|
||||||
|
const trafficWS = createReconnectingWS(`${wsEndpointURL()}/traffic`)
|
||||||
|
|
||||||
|
const trafficWSMessageEvent = createEventSignal<{
|
||||||
|
message: WebSocketEventMap['message']
|
||||||
|
}>(trafficWS, 'message')
|
||||||
|
|
||||||
|
const traffic = () => {
|
||||||
|
const data = trafficWSMessageEvent()?.data
|
||||||
|
|
||||||
|
return data ? (JSON.parse(data) as { down: number; up: number }) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const t = traffic()
|
||||||
|
|
||||||
|
if (t) {
|
||||||
|
setTraffics((traffics) => [...traffics, t].slice(-100))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const trafficChartOptions = createMemo<ApexOptions>(() => ({
|
||||||
|
title: { text: 'Traffic', align: 'center', style: { color: 'gray' } },
|
||||||
|
...defaultChartOptions,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const trafficChartSeries = createMemo(() => [
|
||||||
|
{
|
||||||
|
name: 'Down',
|
||||||
|
data: traffics().map((t) => t.down),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Up',
|
||||||
|
data: traffics().map((t) => t.up),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const memoryWS = createReconnectingWS(`${wsEndpointURL()}/memory`)
|
||||||
|
|
||||||
|
const memoryWSMessageEvent = createEventSignal<{
|
||||||
|
message: WebSocketEventMap['message']
|
||||||
|
}>(memoryWS, 'message')
|
||||||
|
|
||||||
|
const memory = () => {
|
||||||
|
const data = memoryWSMessageEvent()?.data
|
||||||
|
|
||||||
|
return data ? (JSON.parse(data) as { inuse: number }).inuse : null
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const m = memory()
|
||||||
|
|
||||||
|
if (m) {
|
||||||
|
setMemories((memories) => [...memories, m].slice(-100))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const memoryChartOptions = createMemo<ApexOptions>(() => ({
|
||||||
|
title: { text: 'Memory', align: 'center', style: { color: 'gray' } },
|
||||||
|
...defaultChartOptions,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const memoryChartSeries = createMemo(() => [
|
||||||
|
{
|
||||||
|
name: 'memory',
|
||||||
|
data: memories(),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
const connectionsWS = createReconnectingWS(`${wsEndpointURL()}/connections`)
|
||||||
|
|
||||||
|
const connectionsWSMessageEvent = createEventSignal<{
|
||||||
|
message: WebSocketEventMap['message']
|
||||||
|
}>(connectionsWS, 'message')
|
||||||
|
|
||||||
|
const connection = () => {
|
||||||
|
const data = connectionsWSMessageEvent()?.data
|
||||||
|
|
||||||
|
return data
|
||||||
|
? (JSON.parse(data) as {
|
||||||
|
downloadTotal: number
|
||||||
|
uploadTotal: number
|
||||||
|
connections: Connection[]
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="stats w-full shadow">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Upload</div>
|
||||||
|
<div class="stat-value">
|
||||||
|
{byteSize(traffic()?.up || 0).toString()}/s
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Download</div>
|
||||||
|
<div class="stat-value">
|
||||||
|
{byteSize(traffic()?.down || 0).toString()}/s
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Upload Total</div>
|
||||||
|
<div class="stat-value">
|
||||||
|
{byteSize(connection()?.uploadTotal || 0).toString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Download Total</div>
|
||||||
|
<div class="stat-value">
|
||||||
|
{byteSize(connection()?.downloadTotal || 0).toString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Active Connections</div>
|
||||||
|
<div class="stat-value">{connection()?.connections.length || 0}</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-title">Memory Usage</div>
|
||||||
|
<div class="stat-value">{byteSize(memory() || 0).toString()}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-auto grid w-full max-w-screen-lg grid-cols-1 gap-4">
|
||||||
|
<SolidApexCharts
|
||||||
|
type="area"
|
||||||
|
options={trafficChartOptions()}
|
||||||
|
series={trafficChartSeries()}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SolidApexCharts
|
||||||
|
type="line"
|
||||||
|
options={memoryChartOptions()}
|
||||||
|
series={memoryChartSeries()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
55
src/pages/Proxies.tsx
Normal file
55
src/pages/Proxies.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { For, createSignal, onMount } from 'solid-js'
|
||||||
|
import { useRequest } from '~/signals'
|
||||||
|
import { Proxy, ProxyProvider } from '~/types'
|
||||||
|
|
||||||
|
export const Proxies = () => {
|
||||||
|
const request = useRequest()
|
||||||
|
const [proxies, setProxies] = createSignal<Proxy[]>([])
|
||||||
|
const [proxyProviders, setProxyProviders] = createSignal<ProxyProvider[]>([])
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
const { proxies } = await request
|
||||||
|
.get('proxies')
|
||||||
|
.json<{ proxies: Record<string, Proxy> }>()
|
||||||
|
|
||||||
|
setProxies(Object.values(proxies))
|
||||||
|
|
||||||
|
const { providers } = await request
|
||||||
|
.get('providers/proxies')
|
||||||
|
.json<{ providers: Record<string, ProxyProvider> }>()
|
||||||
|
|
||||||
|
setProxyProviders(Object.values(providers))
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div>
|
||||||
|
<h1 class="py-4 text-lg font-semibold">Proxies</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-4 gap-2">
|
||||||
|
<For each={proxies()}>
|
||||||
|
{(proxy) => (
|
||||||
|
<div class="card card-bordered card-compact border-secondary p-4">
|
||||||
|
{proxy.name}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h1 class="py-4 text-lg font-semibold">Proxy Providers</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-4 gap-2">
|
||||||
|
<For each={proxyProviders()}>
|
||||||
|
{(proxy) => (
|
||||||
|
<div class="card card-bordered card-compact border-secondary p-4">
|
||||||
|
{proxy.name}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
3
src/pages/Rules.tsx
Normal file
3
src/pages/Rules.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const Rules = () => {
|
||||||
|
return <div>rules</div>
|
||||||
|
}
|
99
src/pages/Setup.tsx
Normal file
99
src/pages/Setup.tsx
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { createForm } from '@felte/solid'
|
||||||
|
import { validator } from '@felte/validator-zod'
|
||||||
|
import { useNavigate } from '@solidjs/router'
|
||||||
|
import { IconX } from '@tabler/icons-solidjs'
|
||||||
|
import ky from 'ky'
|
||||||
|
import { For } from 'solid-js'
|
||||||
|
import { v4 as uuid } from 'uuid'
|
||||||
|
import { z } from 'zod'
|
||||||
|
import { endpointList, setEndpointList, setSelectedEndpoint } from '~/signals'
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
url: z.string().url().nonempty(),
|
||||||
|
secret: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Setup = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { form } = createForm<z.infer<typeof schema>>({
|
||||||
|
extend: validator({ schema }),
|
||||||
|
async onSubmit(values) {
|
||||||
|
const { hello } = await ky.get(values.url).json<{ hello: string }>()
|
||||||
|
|
||||||
|
if (!hello) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setEndpointList([
|
||||||
|
{
|
||||||
|
id: uuid(),
|
||||||
|
url: values.url,
|
||||||
|
secret: '',
|
||||||
|
},
|
||||||
|
...endpointList(),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const onRemove = (id: string) =>
|
||||||
|
setEndpointList(endpointList().filter((e) => e.id !== id))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="mx-auto flex w-2/3 flex-col items-center gap-4 py-10">
|
||||||
|
<form class="contents" use:form={form}>
|
||||||
|
<div class="join flex w-full">
|
||||||
|
<input
|
||||||
|
name="url"
|
||||||
|
type="url"
|
||||||
|
class="input join-item input-bordered flex-1"
|
||||||
|
placeholder="http://127.0.0.1:9000"
|
||||||
|
list="defaultEndpoints"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<datalist id="defaultEndpoints">
|
||||||
|
<option value="http://127.0.0.1:9000" />
|
||||||
|
</datalist>
|
||||||
|
|
||||||
|
<input
|
||||||
|
name="secret"
|
||||||
|
type="password"
|
||||||
|
autocomplete="new-password"
|
||||||
|
class="input join-item input-bordered"
|
||||||
|
placeholder="secret"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary join-item uppercase">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="flex w-full flex-col gap-4">
|
||||||
|
<For each={endpointList()}>
|
||||||
|
{({ id, url }) => (
|
||||||
|
<div
|
||||||
|
class="badge badge-info flex w-full cursor-pointer items-center gap-4 py-4"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedEndpoint(id)
|
||||||
|
navigate('/')
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-circle btn-ghost btn-xs text-white"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
onRemove(id)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconX />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
44
src/signals/index.ts
Normal file
44
src/signals/index.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { makePersisted } from '@solid-primitives/storage'
|
||||||
|
import ky from 'ky'
|
||||||
|
import { createSignal } from 'solid-js'
|
||||||
|
import { themes } from '~/constants'
|
||||||
|
|
||||||
|
export const [selectedEndpoint, setSelectedEndpoint] = makePersisted(
|
||||||
|
createSignal(''),
|
||||||
|
{
|
||||||
|
name: 'selectedEndpoint',
|
||||||
|
storage: localStorage,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const [endpointList, setEndpointList] = makePersisted(
|
||||||
|
createSignal<
|
||||||
|
{
|
||||||
|
id: string
|
||||||
|
url: string
|
||||||
|
secret: string
|
||||||
|
}[]
|
||||||
|
>([]),
|
||||||
|
{ name: 'endpointList', storage: localStorage },
|
||||||
|
)
|
||||||
|
|
||||||
|
export const [curTheme, setCurTheme] = makePersisted(
|
||||||
|
createSignal<(typeof themes)[number]>('business'),
|
||||||
|
{ name: 'theme', storage: localStorage },
|
||||||
|
)
|
||||||
|
|
||||||
|
export const endpoint = () =>
|
||||||
|
endpointList().find(({ id }) => id === selectedEndpoint())
|
||||||
|
|
||||||
|
export const wsEndpointURL = () => endpoint()?.url.replace('http', 'ws')
|
||||||
|
|
||||||
|
export const useRequest = () => {
|
||||||
|
const e = endpoint()
|
||||||
|
|
||||||
|
return ky.create({
|
||||||
|
prefixUrl: e?.url,
|
||||||
|
headers: {
|
||||||
|
Authorization: e?.secret ? `Bearer ${e.secret}` : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
124
src/types.d.ts
vendored
Normal file
124
src/types.d.ts
vendored
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
declare module 'solid-js' {
|
||||||
|
namespace JSX {
|
||||||
|
interface Directives {
|
||||||
|
form: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Proxy = {
|
||||||
|
name: string
|
||||||
|
type: string
|
||||||
|
all?: string[]
|
||||||
|
extra: Record<string, unknown>
|
||||||
|
history: {
|
||||||
|
time: string
|
||||||
|
delay: number
|
||||||
|
}[]
|
||||||
|
udp: boolean
|
||||||
|
xudp: boolean
|
||||||
|
tfo: boolean
|
||||||
|
now: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProxyProvider = {
|
||||||
|
name: string
|
||||||
|
proxies: {
|
||||||
|
alive: boolean
|
||||||
|
type: string
|
||||||
|
name: string
|
||||||
|
tfo: boolean
|
||||||
|
udp: boolean
|
||||||
|
xudp: boolean
|
||||||
|
id: string
|
||||||
|
extra: Record<string, unknown>
|
||||||
|
history: {
|
||||||
|
time: string
|
||||||
|
delay: number
|
||||||
|
}[]
|
||||||
|
}[]
|
||||||
|
testUrl: string
|
||||||
|
updatedAt: string
|
||||||
|
vehicleType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RuleProvider = {
|
||||||
|
behavior: string
|
||||||
|
format: string
|
||||||
|
name: string
|
||||||
|
ruleCount: number
|
||||||
|
type: string
|
||||||
|
updatedAt: string
|
||||||
|
vehicleType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Connection = {
|
||||||
|
id: string
|
||||||
|
download: number
|
||||||
|
upload: number
|
||||||
|
chains: string[]
|
||||||
|
rule: string
|
||||||
|
rulePayload: string
|
||||||
|
start: string
|
||||||
|
metadata: {
|
||||||
|
network: string
|
||||||
|
type: string
|
||||||
|
destinationIP: string
|
||||||
|
destinationPort: string
|
||||||
|
dnsMode: string
|
||||||
|
host: string
|
||||||
|
inboundIP: string
|
||||||
|
inboundName: string
|
||||||
|
inboundPort: string
|
||||||
|
inboundUser: string
|
||||||
|
process: string
|
||||||
|
processPath: string
|
||||||
|
remoteDestination: string
|
||||||
|
sniffHost: string
|
||||||
|
sourceIP: string
|
||||||
|
sourcePort: string
|
||||||
|
specialProxy: string
|
||||||
|
specialRules: string
|
||||||
|
uid: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Config = {
|
||||||
|
port: number
|
||||||
|
'socks-port': number
|
||||||
|
'redir-port': number
|
||||||
|
'tproxy-port': number
|
||||||
|
'mixed-port': number
|
||||||
|
tun: {
|
||||||
|
enable: boolean
|
||||||
|
device: string
|
||||||
|
stack: string
|
||||||
|
'dns-hijack': null
|
||||||
|
'auto-route': boolean
|
||||||
|
'auto-detect-interface': boolean
|
||||||
|
'file-descriptor': number
|
||||||
|
}
|
||||||
|
'tuic-server': {
|
||||||
|
enable: boolean
|
||||||
|
listen: string
|
||||||
|
certificate: string
|
||||||
|
'private-key': string
|
||||||
|
}
|
||||||
|
'ss-config': string
|
||||||
|
'vmess-config': string
|
||||||
|
authentication: null
|
||||||
|
'allow-lan': boolean
|
||||||
|
'bind-address': string
|
||||||
|
'inbound-tfo': boolean
|
||||||
|
mode: 'rule' | 'global'
|
||||||
|
UnifiedDelay: boolean
|
||||||
|
'log-level': string
|
||||||
|
ipv6: boolean
|
||||||
|
'interface-name': string
|
||||||
|
'geodata-mode': boolean
|
||||||
|
'geodata-loader': string
|
||||||
|
'tcp-concurrent': boolean
|
||||||
|
'find-process-mode': string
|
||||||
|
sniffing: boolean
|
||||||
|
'global-client-fingerprint': boolean
|
||||||
|
}
|
37
tailwind.config.ts
Normal file
37
tailwind.config.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
export default {
|
||||||
|
content: ['./src/**/*.{css,tsx}'],
|
||||||
|
plugins: [require('daisyui')],
|
||||||
|
daisyui: {
|
||||||
|
themes: [
|
||||||
|
'light',
|
||||||
|
'dark',
|
||||||
|
'cupcake',
|
||||||
|
'bumblebee',
|
||||||
|
'emerald',
|
||||||
|
'corporate',
|
||||||
|
'synthwave',
|
||||||
|
'retro',
|
||||||
|
'cyberpunk',
|
||||||
|
'valentine',
|
||||||
|
'halloween',
|
||||||
|
'garden',
|
||||||
|
'forest',
|
||||||
|
'aqua',
|
||||||
|
'lofi',
|
||||||
|
'pastel',
|
||||||
|
'fantasy',
|
||||||
|
'wireframe',
|
||||||
|
'black',
|
||||||
|
'luxury',
|
||||||
|
'dracula',
|
||||||
|
'cmyk',
|
||||||
|
'autumn',
|
||||||
|
'business',
|
||||||
|
'acid',
|
||||||
|
'lemonade',
|
||||||
|
'night',
|
||||||
|
'coffee',
|
||||||
|
'winter',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"strict": true,
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"jsxImportSource": "solid-js",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"noEmit": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"paths": {
|
||||||
|
"~/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
vite.config.ts
Normal file
12
vite.config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import devtools from 'solid-devtools/vite'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import solidPlugin from 'vite-plugin-solid'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'~': '/src',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [devtools(), solidPlugin()],
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user