From 88e6de9d7e52b47f5faf6463733296f3d76d2fa3 Mon Sep 17 00:00:00 2001 From: "Sijie.Sun" Date: Sun, 10 Nov 2024 11:06:58 +0800 Subject: [PATCH] make all frontend functions works (#466) --- Cargo.lock | 1 + easytier-web/Cargo.toml | 1 + easytier-web/frontend-lib/package.json | 1 + .../src/components/HumanEvent.vue | 35 +++ .../frontend-lib/src/components/Status.vue | 9 +- .../frontend-lib/src/easytier-frontend-lib.ts | 8 +- .../src/modules/api.ts | 80 ++++-- .../frontend-lib/src/modules/utils.ts | 86 +++++++ .../frontend-lib/src/types/network.ts | 8 +- easytier-web/frontend/index.html | 4 +- easytier-web/frontend/package.json | 3 +- easytier-web/frontend/public/easytier.png | Bin 0 -> 21114 bytes easytier-web/frontend/public/vite.svg | 1 - easytier-web/frontend/src/App.vue | 112 +-------- easytier-web/frontend/src/assets/easytier.png | Bin 0 -> 21114 bytes easytier-web/frontend/src/assets/vue.svg | 1 - .../src/components/ChangePassword.vue | 33 +++ .../frontend/src/components/Dashboard.vue | 65 +++++ .../frontend/src/components/DeviceList.vue | 237 ++++-------------- .../src/components/DeviceManagement.vue | 197 +++++++++++++++ .../frontend/src/components/Login.vue | 119 +++++---- .../frontend/src/components/MainPage.vue | 173 +++++++++++++ easytier-web/frontend/src/main.ts | 68 ++++- easytier-web/frontend/tsconfig.app.json | 2 +- easytier-web/src/client_manager/session.rs | 21 +- easytier-web/src/client_manager/storage.rs | 16 +- .../db/entity/user_running_network_configs.rs | 2 + easytier-web/src/db/mod.rs | 20 +- .../src/migrator/m20241029_000001_init.rs | 90 +------ easytier-web/src/restful/auth.rs | 17 ++ easytier-web/src/restful/mod.rs | 28 ++- easytier-web/src/restful/network.rs | 43 +++- easytier/src/launcher.rs | 21 +- easytier/src/proto/web.proto | 4 +- easytier/src/web_client/controller.rs | 5 +- pnpm-lock.yaml | 11 + 36 files changed, 1039 insertions(+), 483 deletions(-) create mode 100644 easytier-web/frontend-lib/src/components/HumanEvent.vue rename easytier-web/{frontend => frontend-lib}/src/modules/api.ts (60%) create mode 100644 easytier-web/frontend/public/easytier.png delete mode 100644 easytier-web/frontend/public/vite.svg create mode 100644 easytier-web/frontend/src/assets/easytier.png delete mode 100644 easytier-web/frontend/src/assets/vue.svg create mode 100644 easytier-web/frontend/src/components/ChangePassword.vue create mode 100644 easytier-web/frontend/src/components/Dashboard.vue create mode 100644 easytier-web/frontend/src/components/DeviceManagement.vue create mode 100644 easytier-web/frontend/src/components/MainPage.vue diff --git a/Cargo.lock b/Cargo.lock index 252f03b..54cb749 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2006,6 +2006,7 @@ dependencies = [ "sea-orm", "sea-orm-migration", "serde", + "serde_json", "sqlx", "thiserror", "tokio", diff --git a/easytier-web/Cargo.toml b/easytier-web/Cargo.toml index 9d1cde5..1212a0a 100644 --- a/easytier-web/Cargo.toml +++ b/easytier-web/Cargo.toml @@ -43,6 +43,7 @@ clap = { version = "4.4.8", features = [ "wrap_help", ] } serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" uuid = { version = "1.5.0", features = [ "v4", "fast-rng", diff --git a/easytier-web/frontend-lib/package.json b/easytier-web/frontend-lib/package.json index b9a27b6..782334b 100644 --- a/easytier-web/frontend-lib/package.json +++ b/easytier-web/frontend-lib/package.json @@ -21,6 +21,7 @@ "@primevue/themes": "^4.2.1", "@vueuse/core": "^11.1.0", "aura": "link:@primevue\\themes\\aura", + "axios": "^1.7.7", "ip-num": "1.5.1", "primeicons": "^7.0.0", "primevue": "^4.2.1", diff --git a/easytier-web/frontend-lib/src/components/HumanEvent.vue b/easytier-web/frontend-lib/src/components/HumanEvent.vue new file mode 100644 index 0000000..b5ddc48 --- /dev/null +++ b/easytier-web/frontend-lib/src/components/HumanEvent.vue @@ -0,0 +1,35 @@ + + + diff --git a/easytier-web/frontend-lib/src/components/Status.vue b/easytier-web/frontend-lib/src/components/Status.vue index da84063..538ab2d 100644 --- a/easytier-web/frontend-lib/src/components/Status.vue +++ b/easytier-web/frontend-lib/src/components/Status.vue @@ -181,7 +181,7 @@ const myNodeInfoChips = computed(() => { const listeners = my_node_info.listeners for (const [idx, listener] of listeners?.entries()) { chips.push({ - label: `Listener ${idx}: ${listener}`, + label: `Listener ${idx}: ${listener.url}`, icon: '', } as Chip) } @@ -295,7 +295,7 @@ function showEventLogs() { if (!detail) return - dialogContent.value = detail.events + dialogContent.value = detail.events.map((event: string) => JSON.parse(event)) dialogHeader.value = 'event_log' dialogVisible.value = true } @@ -309,10 +309,11 @@ function showEventLogs() { diff --git a/easytier-web/frontend-lib/src/easytier-frontend-lib.ts b/easytier-web/frontend-lib/src/easytier-frontend-lib.ts index 697f047..ae3be67 100644 --- a/easytier-web/frontend-lib/src/easytier-frontend-lib.ts +++ b/easytier-web/frontend-lib/src/easytier-frontend-lib.ts @@ -7,6 +7,10 @@ import PrimeVue from 'primevue/config' import I18nUtils from './modules/i18n' import * as NetworkTypes from './types/network' +import HumanEvent from './components/HumanEvent.vue'; +import Tooltip from 'primevue/tooltip'; +import * as Api from './modules/api'; +import * as Utils from './modules/utils'; export default { install: (app: App) => { @@ -27,7 +31,9 @@ export default { app.component('Config', Config); app.component('Status', Status); + app.component('HumanEvent', HumanEvent); + app.directive('tooltip', Tooltip); } }; -export { Config, Status, I18nUtils, NetworkTypes }; +export { Config, Status, I18nUtils, NetworkTypes, Api, Utils }; diff --git a/easytier-web/frontend/src/modules/api.ts b/easytier-web/frontend-lib/src/modules/api.ts similarity index 60% rename from easytier-web/frontend/src/modules/api.ts rename to easytier-web/frontend-lib/src/modules/api.ts index 5d1c91b..a507fb0 100644 --- a/easytier-web/frontend/src/modules/api.ts +++ b/easytier-web/frontend-lib/src/modules/api.ts @@ -11,8 +11,8 @@ export interface LoginResponse { } export interface RegisterResponse { + success: boolean; message: string; - user: any; // 同上 } // 定义请求体数据结构 @@ -22,21 +22,27 @@ export interface Credential { } export interface RegisterData { - credential: Credential; + credentials: Credential; captcha: string; } -class ApiClient { - private client: AxiosInstance; +export interface Summary { + device_count: number; +} - constructor(baseUrl: string) { +export class ApiClient { + private client: AxiosInstance; + private authFailedCb: Function | undefined; + + constructor(baseUrl: string, authFailedCb: Function | undefined = undefined) { this.client = axios.create({ - baseURL: baseUrl, + baseURL: baseUrl + '/api/v1', withCredentials: true, // 如果需要支持跨域携带cookie headers: { 'Content-Type': 'application/json', }, }); + this.authFailedCb = authFailedCb; // 添加请求拦截器 this.client.interceptors.request.use((config: InternalAxiosRequestConfig) => { @@ -47,12 +53,18 @@ class ApiClient { // 添加响应拦截器 this.client.interceptors.response.use((response: AxiosResponse) => { - console.log('Axios Response:', response); + console.debug('Axios Response:', response); return response.data; // 假设服务器返回的数据都在data属性中 }, (error: any) => { if (error.response) { - // 请求已发出,但是服务器响应的状态码不在2xx范围 - console.error('Response Error:', error.response.data); + let response: AxiosResponse = error.response; + if (response.status == 401 && this.authFailedCb) { + console.error('Unauthorized:', response.data); + this.authFailedCb(); + } else { + // 请求已发出,但是服务器响应的状态码不在2xx范围 + console.error('Response Error:', error.response.data); + } } else if (error.request) { // 请求已发出,但是没有收到响应 console.error('Request Error:', error.request); @@ -64,6 +76,20 @@ class ApiClient { }); } + // 注册 + public async register(data: RegisterData): Promise { + try { + const response = await this.client.post('/auth/register', data); + console.log("register response:", response); + return { success: true, message: 'Register success', }; + } catch (error) { + if (error instanceof AxiosError) { + return { success: false, message: 'Failed to register, error: ' + JSON.stringify(error.response?.data), }; + } + return { success: false, message: 'Unknown error, error: ' + error, }; + } + } + // 登录 public async login(data: Credential): Promise { try { @@ -82,10 +108,24 @@ class ApiClient { } } - // 注册 - public async register(data: RegisterData): Promise { - const response = await this.client.post('/auth/register', data); - return response.data; + public async logout() { + await this.client.get('/auth/logout'); + if (this.authFailedCb) { + this.authFailedCb(); + } + } + + public async change_password(new_password: string) { + await this.client.put('/auth/password', { new_password: new_password }); + } + + public async check_login_status() { + try { + await this.client.get('/auth/check_login_status'); + return true; + } catch (error) { + return false; + } } public async list_session() { @@ -103,6 +143,11 @@ class ApiClient { return response.info.map; } + public async get_network_config(machine_id: string, inst_id: string): Promise { + const response = await this.client.get>('/machines/' + machine_id + '/networks/config/' + inst_id); + return response; + } + public async validate_config(machine_id: string, config: any): Promise { const response = await this.client.post(`/machines/${machine_id}/validate-config`, { config: config, @@ -110,7 +155,7 @@ class ApiClient { return response; } - public async run_network(machine_id: string, config: string): Promise { + public async run_network(machine_id: string, config: any): Promise { await this.client.post(`/machines/${machine_id}/networks`, { config: config, }); @@ -120,8 +165,13 @@ class ApiClient { await this.client.delete(`/machines/${machine_id}/networks/${inst_id}`); } + public async get_summary(): Promise { + const response = await this.client.get('/summary'); + return response; + } + public captcha_url() { - return this.client.defaults.baseURL + 'auth/captcha'; + return this.client.defaults.baseURL + '/auth/captcha'; } } diff --git a/easytier-web/frontend-lib/src/modules/utils.ts b/easytier-web/frontend-lib/src/modules/utils.ts index 85b1532..c2752f2 100644 --- a/easytier-web/frontend-lib/src/modules/utils.ts +++ b/easytier-web/frontend-lib/src/modules/utils.ts @@ -13,3 +13,89 @@ export function num2ipv6(ip: Ipv6Addr) { + BigInt(ip.part4), ) } + +function toHexString(uint64: bigint, padding = 9): string { + let hexString = uint64.toString(16); + while (hexString.length < padding) { + hexString = '0' + hexString; + } + return hexString; +} + +function uint32ToUuid(part1: number, part2: number, part3: number, part4: number): string { + // 将两个 uint64 转换为 16 进制字符串 + const part1Hex = toHexString(BigInt(part1), 8); + const part2Hex = toHexString(BigInt(part2), 8); + const part3Hex = toHexString(BigInt(part3), 8); + const part4Hex = toHexString(BigInt(part4), 8); + + // 构造 UUID 格式字符串 + const uuid = `${part1Hex.substring(0, 8)}-${part2Hex.substring(0, 4)}-${part2Hex.substring(4, 8)}-${part3Hex.substring(0, 4)}-${part3Hex.substring(4, 8)}${part4Hex.substring(0, 12)}`; + + return uuid; +} + +export interface UUID { + part1: number; + part2: number; + part3: number; + part4: number; +} + +export function UuidToStr(uuid: UUID): string { + return uint32ToUuid(uuid.part1, uuid.part2, uuid.part3, uuid.part4); +} + +export interface DeviceInfo { + hostname: string; + public_ip: string; + running_network_count: number; + report_time: string; + easytier_version: string; + running_network_instances?: Array; + machine_id: string; +} + +export function buildDeviceInfo(device: any): DeviceInfo { + let dev_info: DeviceInfo = { + hostname: device.info?.hostname, + public_ip: device.client_url, + running_network_instances: device.info?.running_network_instances.map((instance: any) => UuidToStr(instance)), + running_network_count: device.info?.running_network_instances.length, + report_time: device.info?.report_time, + easytier_version: device.info?.easytier_version, + machine_id: UuidToStr(device.info?.machine_id), + }; + + return dev_info; +} + +// write a class to run a function periodically and can be stopped by calling stop(), use setTimeout to trigger the function +export class PeriodicTask { + private interval: number; + private task: (() => Promise) | undefined; + private timer: any; + + constructor(task: () => Promise, interval: number) { + this.interval = interval; + this.task = task; + } + + _runTaskHelper(nextInterval: number) { + this.timer = setTimeout(async () => { + if (this.task) { + await this.task(); + this._runTaskHelper(this.interval); + } + }, nextInterval); + } + + start() { + this._runTaskHelper(0); + } + + stop() { + this.task = undefined; + clearTimeout(this.timer); + } +} diff --git a/easytier-web/frontend-lib/src/types/network.ts b/easytier-web/frontend-lib/src/types/network.ts index 3fbce7b..f509696 100644 --- a/easytier-web/frontend-lib/src/types/network.ts +++ b/easytier-web/frontend-lib/src/types/network.ts @@ -84,7 +84,7 @@ export interface NetworkInstance { export interface NetworkInstanceRunningInfo { dev_name: string my_node_info: NodeInfo - events: Record + events: Array, node_info: NodeInfo routes: Route[] peers: PeerInfo[] @@ -104,6 +104,10 @@ export interface Ipv6Addr { part4: number } +export interface Url { + url: string +} + export interface NodeInfo { virtual_ipv4: string hostname: string @@ -127,7 +131,7 @@ export interface NodeInfo { }[] } stun_info: StunInfo - listeners: string[] + listeners: Url[] vpn_portal_cfg?: string } diff --git a/easytier-web/frontend/index.html b/easytier-web/frontend/index.html index dde16aa..cc73f06 100644 --- a/easytier-web/frontend/index.html +++ b/easytier-web/frontend/index.html @@ -2,9 +2,9 @@ - + - Vite + Vue + TS + EasyTier Dashboard
diff --git a/easytier-web/frontend/package.json b/easytier-web/frontend/package.json index bd36fd5..730aa08 100644 --- a/easytier-web/frontend/package.json +++ b/easytier-web/frontend/package.json @@ -15,7 +15,8 @@ "easytier-frontend-lib": "workspace:*", "primevue": "^4.2.1", "tailwindcss-primeui": "^0.3.4", - "vue": "^3.5.12" + "vue": "^3.5.12", + "vue-router": "4" }, "devDependencies": { "@types/node": "^22.8.6", diff --git a/easytier-web/frontend/public/easytier.png b/easytier-web/frontend/public/easytier.png new file mode 100644 index 0000000000000000000000000000000000000000..b59ea0c86f828d64b44c082accc94730d6818c12 GIT binary patch literal 21114 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

j%C*FWe;WFmD!K>mg?r*^ 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 R