Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34e4e907a9 | ||
|
|
2f4a097787 | ||
|
|
f3de00be37 | ||
|
|
4cf61f0d4a | ||
|
|
4e5915f98e | ||
|
|
870eca9e9f |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -21,7 +21,7 @@ on:
|
||||
version:
|
||||
description: 'Version for this release'
|
||||
type: string
|
||||
default: 'v2.1.0'
|
||||
default: 'v2.1.1'
|
||||
required: true
|
||||
make_latest:
|
||||
description: 'Mark this release as latest'
|
||||
|
||||
29
Cargo.lock
generated
29
Cargo.lock
generated
@@ -1830,7 +1830,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
|
||||
|
||||
[[package]]
|
||||
name = "easytier"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
@@ -1926,7 +1926,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "easytier-gui"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -1979,6 +1979,7 @@ dependencies = [
|
||||
"axum-login",
|
||||
"axum-messages",
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"clap",
|
||||
"dashmap",
|
||||
"easytier",
|
||||
@@ -1987,12 +1988,14 @@ dependencies = [
|
||||
"password-auth",
|
||||
"rand 0.8.5",
|
||||
"rust-embed",
|
||||
"rust-i18n",
|
||||
"rusttype",
|
||||
"sea-orm",
|
||||
"sea-orm-migration",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"sys-locale",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
@@ -7312,9 +7315,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.1.0"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c24f1ab82d336e09f5f1094a4d9227c99ac26cce263bfdf8136897cc6db6f1d0"
|
||||
checksum = "d3889b392db6d32a105d3757230ea0220090b8f94c90d3e60b6c5eb91178ab1b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -7350,7 +7353,7 @@ dependencies = [
|
||||
"tauri-runtime",
|
||||
"tauri-runtime-wry",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.2",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
"tray-icon",
|
||||
"url",
|
||||
@@ -7562,9 +7565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.2.0"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cce18d43f80d4aba3aa8a0c953bbe835f3d0f2370aca75e8dbb14bd4bab27958"
|
||||
checksum = "a1ef7363e7229ac8d04e8a5d405670dbd43dde8fc4bc3bc56105c35452d03784"
|
||||
dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
@@ -7574,16 +7577,16 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.2",
|
||||
"thiserror 1.0.63",
|
||||
"url",
|
||||
"windows 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime-wry"
|
||||
version = "2.2.0"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f442a38863e10129ffe2cec7bd09c2dcf8a098a3a27801a476a304d5bb991d2"
|
||||
checksum = "62fa2068e8498ad007b54d5773d03d57c3ff6dd96f8c8ce58beff44d0d5e0d30"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http 1.1.0",
|
||||
@@ -9259,13 +9262,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wry"
|
||||
version = "0.47.0"
|
||||
version = "0.46.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "553ca1ce149982123962fac2506aa75b8b76288779a77e72b12fa2fc34938647"
|
||||
checksum = "2f8c948dc5f7c23bd93ba03b85b7f679852589bb78e150424d993171e4ef7b73"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
"dunce",
|
||||
@@ -9290,7 +9292,6 @@ dependencies = [
|
||||
"soup3",
|
||||
"tao-macros",
|
||||
"thiserror 1.0.63",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
"webview2-com",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "easytier-gui",
|
||||
"type": "module",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4",
|
||||
"scripts": {
|
||||
@@ -35,14 +35,16 @@
|
||||
"@primevue/auto-import-resolver": "^4.1.0",
|
||||
"@tauri-apps/api": "2.1.0",
|
||||
"@tauri-apps/cli": "2.1.0",
|
||||
"@types/default-gateway": "^7.2.2",
|
||||
"@types/node": "^22.7.4",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitejs/plugin-vue": "^5.1.4",
|
||||
"@vue-macros/volar": "0.30.5",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"cidr-tools": "^11.0.2",
|
||||
"default-gateway": "^7.2.2",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-plugin-format": "^0.1.2",
|
||||
"internal-ip": "^8.0.0",
|
||||
"postcss": "^8.4.47",
|
||||
"tailwindcss": "^3.4.13",
|
||||
"typescript": "^5.6.2",
|
||||
@@ -58,4 +60,4 @@
|
||||
"vue-i18n": "^10.0.0",
|
||||
"vue-tsc": "^2.1.10"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "easytier-gui"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
description = "EasyTier GUI"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
@@ -15,7 +15,8 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
tauri-build = { version = "2.0.0-rc", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.1", features = [
|
||||
# wry 0.47 may crash on android, see https://github.com/EasyTier/EasyTier/issues/527
|
||||
tauri = { version = "=2.0.6", features = [
|
||||
"tray-icon",
|
||||
"image-png",
|
||||
"image-ico",
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"vpnservice:allow-prepare-vpn",
|
||||
"vpnservice:allow-start-vpn",
|
||||
"vpnservice:allow-stop-vpn",
|
||||
"vpnservice:allow-register-listener",
|
||||
"vpnservice:allow-registerListener",
|
||||
"os:default",
|
||||
"os:allow-os-type",
|
||||
"os:allow-arch",
|
||||
|
||||
@@ -141,7 +141,6 @@ pub fn run() {
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
utils::setup_panic_handler();
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"createUpdaterArtifacts": false
|
||||
},
|
||||
"productName": "easytier-gui",
|
||||
"version": "2.1.0",
|
||||
"version": "2.1.1",
|
||||
"identifier": "com.kkrainbow.easytier",
|
||||
"plugins": {},
|
||||
"app": {
|
||||
|
||||
@@ -49,9 +49,9 @@ async function doStartVpn(ipv4Addr: string, cidr: number, routes: string[]) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log('start vpn')
|
||||
console.log('start vpn service', ipv4Addr, cidr, routes)
|
||||
const start_ret = await start_vpn({
|
||||
ipv4Addr: `${ipv4Addr}`,
|
||||
ipv4Addr: `${ipv4Addr}/${cidr}`,
|
||||
routes,
|
||||
disallowedApplications: ['com.kkrainbow.easytier'],
|
||||
mtu: 1300,
|
||||
@@ -113,6 +113,7 @@ function getRoutesForVpn(routes: Route[]): string[] {
|
||||
}
|
||||
|
||||
async function onNetworkInstanceChange() {
|
||||
console.error('vpn service watch network instance change ids', JSON.stringify(networkStore.networkInstanceIds))
|
||||
const insts = networkStore.networkInstanceIds
|
||||
if (!insts) {
|
||||
await doStopVpn()
|
||||
@@ -142,7 +143,7 @@ async function onNetworkInstanceChange() {
|
||||
const routesChanged = JSON.stringify(routes) !== JSON.stringify(curVpnStatus.routes)
|
||||
|
||||
if (ipChanged || routesChanged) {
|
||||
console.log('virtual ip changed', JSON.stringify(curVpnStatus), virtual_ip)
|
||||
console.info('vpn service virtual ip changed', JSON.stringify(curVpnStatus), virtual_ip)
|
||||
try {
|
||||
await doStopVpn()
|
||||
}
|
||||
@@ -154,7 +155,7 @@ async function onNetworkInstanceChange() {
|
||||
await doStartVpn(virtual_ip, 24, routes)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('start vpn failed, clear all network insts.', e)
|
||||
console.error('start vpn service failed, clear all network insts.', e)
|
||||
networkStore.clearNetworkInstances()
|
||||
await retainNetworkInstance(networkStore.networkInstanceIds)
|
||||
}
|
||||
@@ -175,6 +176,7 @@ async function watchNetworkInstance() {
|
||||
}
|
||||
subscribe_running = false
|
||||
})
|
||||
console.error('vpn service watch network instance')
|
||||
}
|
||||
|
||||
export async function initMobileVpnService() {
|
||||
|
||||
@@ -250,7 +250,12 @@ onBeforeMount(async () => {
|
||||
|
||||
onMounted(async () => {
|
||||
if (type() === 'android') {
|
||||
await initMobileVpnService()
|
||||
try {
|
||||
await initMobileVpnService()
|
||||
console.error("easytier init vpn service done")
|
||||
} catch (e: any) {
|
||||
console.error("easytier init vpn service failed", e)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { networkInterfaces } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import VueI18n from '@intlify/unplugin-vue-i18n/vite'
|
||||
import { PrimeVueResolver } from '@primevue/auto-import-resolver'
|
||||
import Vue from '@vitejs/plugin-vue'
|
||||
import { internalIpV4Sync } from 'internal-ip'
|
||||
import { containsCidr, parseCidr } from 'cidr-tools'
|
||||
import { gateway4sync } from 'default-gateway'
|
||||
import AutoImport from 'unplugin-auto-import/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import VueMacros from 'unplugin-vue-macros/vite'
|
||||
@@ -13,6 +15,20 @@ import { defineConfig } from 'vite'
|
||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
||||
import Layouts from 'vite-plugin-vue-layouts'
|
||||
|
||||
function findIp(gateway: string) {
|
||||
// Look for the matching interface in all local interfaces
|
||||
console.log('gateway', gateway)
|
||||
for (const addresses of Object.values(networkInterfaces())) {
|
||||
if (!addresses)
|
||||
continue
|
||||
for (const { cidr } of addresses) {
|
||||
if (cidr && containsCidr(cidr, gateway)) {
|
||||
return parseCidr(cidr).ip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const host = process.env.TAURI_DEV_HOST
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
@@ -99,10 +115,10 @@ export default defineConfig(async () => ({
|
||||
},
|
||||
hmr: host
|
||||
? {
|
||||
protocol: 'ws',
|
||||
host: internalIpV4Sync(),
|
||||
port: 1430,
|
||||
}
|
||||
protocol: 'ws',
|
||||
host: findIp(gateway4sync().gateway),
|
||||
port: 1430,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
name = "easytier-web"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
|
||||
|
||||
[dependencies]
|
||||
easytier = { path = "../easytier" }
|
||||
@@ -36,6 +37,8 @@ rusttype = "0.9.3"
|
||||
imageproc = "0.23.0"
|
||||
|
||||
|
||||
rust-i18n = "3"
|
||||
sys-locale = "0.3"
|
||||
clap = { version = "4.4.8", features = [
|
||||
"string",
|
||||
"unicode",
|
||||
@@ -50,3 +53,5 @@ uuid = { version = "1.5.0", features = [
|
||||
"macro-diagnostics",
|
||||
"serde",
|
||||
] }
|
||||
|
||||
chrono = { version = "0.4.37", features = ["serde"] }
|
||||
|
||||
@@ -303,7 +303,7 @@ function showEventLogs() {
|
||||
|
||||
<template>
|
||||
<div class="frontend-lib">
|
||||
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-2/3 h-auto">
|
||||
<Dialog v-model:visible="dialogVisible" modal :header="t(dialogHeader)" class="w-2/3 h-auto max-w-full">
|
||||
<ScrollPanel v-if="dialogHeader === 'vpn_portal_config'">
|
||||
<pre>{{ dialogContent }}</pre>
|
||||
</ScrollPanel>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios, { AxiosError, AxiosInstance, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
import { Md5 } from 'ts-md5'
|
||||
import { UUID } from './utils';
|
||||
|
||||
export interface ValidateConfigResponse {
|
||||
toml_config: string;
|
||||
@@ -31,6 +32,11 @@ export interface Summary {
|
||||
device_count: number;
|
||||
}
|
||||
|
||||
export interface ListNetworkInstanceIdResponse {
|
||||
running_inst_ids: Array<UUID>,
|
||||
disabled_inst_ids: Array<UUID>,
|
||||
}
|
||||
|
||||
export class ApiClient {
|
||||
private client: AxiosInstance;
|
||||
private authFailedCb: Function | undefined;
|
||||
@@ -141,6 +147,17 @@ export class ApiClient {
|
||||
return response.machines;
|
||||
}
|
||||
|
||||
public async list_deivce_instance_ids(machine_id: string): Promise<ListNetworkInstanceIdResponse> {
|
||||
const response = await this.client.get<any, ListNetworkInstanceIdResponse>('/machines/' + machine_id + '/networks');
|
||||
return response;
|
||||
}
|
||||
|
||||
public async update_device_instance_state(machine_id: string, inst_id: string, disabled: boolean): Promise<undefined> {
|
||||
await this.client.put<string>('/machines/' + machine_id + '/networks/' + inst_id, {
|
||||
disabled: disabled,
|
||||
});
|
||||
}
|
||||
|
||||
public async get_network_info(machine_id: string, inst_id: string): Promise<any> {
|
||||
const response = await this.client.get<any, Record<string, any>>('/machines/' + machine_id + '/networks/info/' + inst_id);
|
||||
return response.info.map;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Toolbar, IftaLabel, Select, Button, ConfirmPopup, Dialog, useConfirm, useToast } from 'primevue';
|
||||
import { NetworkTypes, Status, Utils, Api, } from 'easytier-frontend-lib';
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { watch, computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -33,9 +33,16 @@ const isEditing = ref(false);
|
||||
const showCreateNetworkDialog = ref(false);
|
||||
const newNetworkConfig = ref<NetworkTypes.NetworkConfig>(NetworkTypes.DEFAULT_NETWORK_CONFIG());
|
||||
|
||||
const listInstanceIdResponse = ref<Api.ListNetworkInstanceIdResponse | undefined>(undefined);
|
||||
|
||||
const instanceIdList = computed(() => {
|
||||
let insts = deviceInfo.value?.running_network_instances || [];
|
||||
let options = insts.map((instance: string) => {
|
||||
let insts = new Set(deviceInfo.value?.running_network_instances || []);
|
||||
let t = listInstanceIdResponse.value;
|
||||
if (t) {
|
||||
t.running_inst_ids.forEach((u) => insts.add(Utils.UuidToStr(u)));
|
||||
t.disabled_inst_ids.forEach((u) => insts.add(Utils.UuidToStr(u)));
|
||||
}
|
||||
let options = Array.from(insts).map((instance: string) => {
|
||||
return { uuid: instance };
|
||||
});
|
||||
return options;
|
||||
@@ -51,6 +58,53 @@ const selectedInstanceId = computed({
|
||||
}
|
||||
});
|
||||
|
||||
const needShowNetworkStatus = computed(() => {
|
||||
if (!selectedInstanceId.value) {
|
||||
// nothing selected
|
||||
return false;
|
||||
}
|
||||
if (networkIsDisabled.value) {
|
||||
// network is disabled
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
|
||||
const networkIsDisabled = computed(() => {
|
||||
if (!selectedInstanceId.value) {
|
||||
return false;
|
||||
}
|
||||
return listInstanceIdResponse.value?.disabled_inst_ids.map(Utils.UuidToStr).includes(selectedInstanceId.value?.uuid);
|
||||
});
|
||||
|
||||
watch(selectedInstanceId, async (newVal, oldVal) => {
|
||||
if (newVal?.uuid !== oldVal?.uuid && networkIsDisabled.value) {
|
||||
await loadDisabledNetworkConfig();
|
||||
}
|
||||
});
|
||||
|
||||
const disabledNetworkConfig = ref<NetworkTypes.NetworkConfig | undefined>(undefined);
|
||||
|
||||
const loadDisabledNetworkConfig = async () => {
|
||||
disabledNetworkConfig.value = undefined;
|
||||
|
||||
if (!deviceId.value || !selectedInstanceId.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ret = await props.api?.get_network_config(deviceId.value, selectedInstanceId.value.uuid);
|
||||
disabledNetworkConfig.value = ret;
|
||||
}
|
||||
|
||||
const updateNetworkState = async (disabled: boolean) => {
|
||||
if (!deviceId.value || !selectedInstanceId.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await props.api?.update_device_instance_state(deviceId.value, selectedInstanceId.value.uuid, disabled);
|
||||
await loadNetworkInstanceIds();
|
||||
}
|
||||
|
||||
const confirm = useConfirm();
|
||||
const confirmDeleteNetwork = (event: any) => {
|
||||
confirm.require({
|
||||
@@ -128,6 +182,15 @@ const editNetwork = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const loadNetworkInstanceIds = async () => {
|
||||
if (!deviceId.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
listInstanceIdResponse.value = await props.api?.list_deivce_instance_ids(deviceId.value);
|
||||
console.debug("loadNetworkInstanceIds", listInstanceIdResponse.value);
|
||||
}
|
||||
|
||||
const loadDeviceInfo = async () => {
|
||||
if (!deviceId.value || !instanceId.value) {
|
||||
return;
|
||||
@@ -146,7 +209,7 @@ const loadDeviceInfo = async () => {
|
||||
|
||||
let periodFunc = new Utils.PeriodicTask(async () => {
|
||||
try {
|
||||
await loadDeviceInfo();
|
||||
await Promise.all([loadNetworkInstanceIds(), loadDeviceInfo()]);
|
||||
} catch (e) {
|
||||
console.debug(e);
|
||||
}
|
||||
@@ -188,8 +251,23 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</Toolbar>
|
||||
|
||||
<Status v-bind:cur-network-inst="curNetworkInfo" v-if="!!selectedInstanceId">
|
||||
</Status>
|
||||
<!-- For running network, show the status -->
|
||||
<div v-if="needShowNetworkStatus">
|
||||
<Status v-bind:cur-network-inst="curNetworkInfo" v-if="needShowNetworkStatus">
|
||||
</Status>
|
||||
<center>
|
||||
<Button @click="updateNetworkState(true)" label="Disable Network" severity="warn" />
|
||||
</center>
|
||||
</div>
|
||||
|
||||
<!-- For disabled network, show the config -->
|
||||
<div v-if="networkIsDisabled">
|
||||
<Config :cur-network="disabledNetworkConfig" @run-network="updateNetworkState(false)"
|
||||
v-if="disabledNetworkConfig" />
|
||||
<div v-else>
|
||||
<div class="text-center text-xl"> Network is disabled, Loading config... </div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 place-content-center h-full" v-if="!selectedInstanceId">
|
||||
<div class="text-center text-xl"> Select or create a network instance to manage </div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { Card, InputText, Password, Button, AutoComplete } from 'primevue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
@@ -20,8 +20,60 @@ const registerPassword = ref('');
|
||||
const captcha = ref('');
|
||||
const captchaSrc = computed(() => api.value.captcha_url());
|
||||
|
||||
interface ApiHost {
|
||||
value: string;
|
||||
usedAt: number;
|
||||
}
|
||||
|
||||
const isValidHttpUrl = (s: string): boolean => {
|
||||
let url;
|
||||
|
||||
try {
|
||||
url = new URL(s);
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.protocol === "http:" || url.protocol === "https:";
|
||||
}
|
||||
|
||||
const cleanAndLoadApiHosts = (): Array<ApiHost> => {
|
||||
const maxHosts = 10;
|
||||
const apiHosts = localStorage.getItem('apiHosts');
|
||||
if (apiHosts) {
|
||||
const hosts: Array<ApiHost> = JSON.parse(apiHosts);
|
||||
// sort by usedAt
|
||||
hosts.sort((a, b) => b.usedAt - a.usedAt);
|
||||
|
||||
// only keep the first 10
|
||||
if (hosts.length > maxHosts) {
|
||||
hosts.splice(maxHosts);
|
||||
}
|
||||
|
||||
localStorage.setItem('apiHosts', JSON.stringify(hosts));
|
||||
return hosts;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const saveApiHost = (host: string) => {
|
||||
console.log('Save API Host:', host);
|
||||
if (!isValidHttpUrl(host)) {
|
||||
console.error('Invalid API Host:', host);
|
||||
return;
|
||||
}
|
||||
|
||||
let hosts = cleanAndLoadApiHosts();
|
||||
const newHost: ApiHost = { value: host, usedAt: Date.now() };
|
||||
hosts = hosts.filter((h) => h.value !== host);
|
||||
hosts.push(newHost);
|
||||
localStorage.setItem('apiHosts', JSON.stringify(hosts));
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
// Add your login logic here
|
||||
saveApiHost(apiHost.value);
|
||||
const credential: Api.Credential = { username: username.value, password: password.value, };
|
||||
let ret = await api.value?.login(credential);
|
||||
if (ret.success) {
|
||||
@@ -36,6 +88,7 @@ const onSubmit = async () => {
|
||||
};
|
||||
|
||||
const onRegister = async () => {
|
||||
saveApiHost(apiHost.value);
|
||||
const credential: Api.Credential = { username: registerUsername.value, password: registerPassword.value };
|
||||
const registerReq: Api.RegisterData = { credentials: credential, captcha: captcha.value };
|
||||
let ret = await api.value?.register(registerReq);
|
||||
@@ -47,17 +100,36 @@ const onRegister = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getInitialApiHost = (): string => {
|
||||
const hosts = cleanAndLoadApiHosts();
|
||||
if (hosts.length > 0) {
|
||||
return hosts[0].value;
|
||||
} else {
|
||||
return defaultApiHost;
|
||||
}
|
||||
};
|
||||
|
||||
const defaultApiHost = 'https://config-server.easytier.cn'
|
||||
const apiHost = ref<string>(defaultApiHost)
|
||||
const apiHost = ref<string>(getInitialApiHost())
|
||||
const apiHostSuggestions = ref<Array<string>>([])
|
||||
const apiHostSearch = async (event: { query: string }) => {
|
||||
apiHostSuggestions.value = [];
|
||||
let hosts = cleanAndLoadApiHosts();
|
||||
if (event.query) {
|
||||
apiHostSuggestions.value.push(event.query);
|
||||
}
|
||||
apiHostSuggestions.value.push(defaultApiHost);
|
||||
hosts.forEach((host) => {
|
||||
apiHostSuggestions.value.push(host.value);
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let hosts = cleanAndLoadApiHosts();
|
||||
if (hosts.length === 0) {
|
||||
saveApiHost(defaultApiHost);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -87,7 +159,7 @@ const apiHostSearch = async (event: { query: string }) => {
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<Button label="Register" type="button" class="w-full"
|
||||
@click="$router.replace({ name: 'register' })" severity="secondary" />
|
||||
@click="saveApiHost(apiHost); $router.replace({ name: 'register' })" severity="secondary" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -111,7 +183,7 @@ const apiHostSearch = async (event: { query: string }) => {
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<Button label="Back to Login" type="button" class="w-full"
|
||||
@click="$router.replace({ name: 'login' })" severity="secondary" />
|
||||
@click="saveApiHost(apiHost); $router.replace({ name: 'login' })" severity="secondary" />
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
24
easytier-web/locales/app.yml
Normal file
24
easytier-web/locales/app.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
_version: 2
|
||||
|
||||
cli:
|
||||
db:
|
||||
en: "path to the sqlite3 database file, used to save all the data"
|
||||
zh-CN: "sqlite3 数据库文件路径, 用于保存所有数据"
|
||||
console_log_level:
|
||||
en: "The log level for the console logger. Possible values: trace, debug, info, warn, error"
|
||||
zh-CN: "控制台日志级别。可能的值:trace, debug, info, warn, error"
|
||||
file_log_level:
|
||||
en: "The log level for the file logger. Possible values: trace, debug, info, warn, error"
|
||||
zh-CN: "文件日志级别。可能的值:trace, debug, info, warn, error"
|
||||
file_log_dir:
|
||||
en: "The directory to save the log files, default is the current directory"
|
||||
zh-CN: "保存日志文件的目录,默认为当前目录"
|
||||
config_server_port:
|
||||
en: "The port to listen for the config server, used by the easytier-core to connect to"
|
||||
zh-CN: "配置服务器的监听端口,用于被 easytier-core 连接"
|
||||
config_server_protocol:
|
||||
en: "The protocol to listen for the config server, used by the easytier-core to connect to"
|
||||
zh-CN: "配置服务器的监听协议,用于被 easytier-core 连接, 可能的值:udp, tcp"
|
||||
api_server_port:
|
||||
en: "The port to listen for the restful server, acting as ApiHost and used by the web frontend"
|
||||
zh-CN: "restful 服务器的监听端口,作为 ApiHost 并被 web 前端使用"
|
||||
@@ -93,7 +93,7 @@ impl ClientManager {
|
||||
.map(|item| item.value().clone())
|
||||
}
|
||||
|
||||
pub fn list_machine_by_token(&self, token: String) -> Vec<url::Url> {
|
||||
pub async fn list_machine_by_token(&self, token: String) -> Vec<url::Url> {
|
||||
self.storage.list_token_clients(&token)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
use std::{fmt::Debug, str::FromStr as _, sync::Arc};
|
||||
|
||||
use easytier::{
|
||||
common::scoped_task::ScopedTask,
|
||||
@@ -15,6 +15,8 @@ use easytier::{
|
||||
};
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
|
||||
use crate::db::ListNetworkProps;
|
||||
|
||||
use super::storage::{Storage, StorageToken, WeakRefStorage};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -87,10 +89,20 @@ impl WebServerService for SessionRpcService {
|
||||
.map(Into::into)
|
||||
.unwrap_or(uuid::Uuid::new_v4()),
|
||||
});
|
||||
if let Ok(storage) = Storage::try_from(data.storage.clone()) {
|
||||
storage.add_client(data.storage_token.as_ref().unwrap().clone());
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(storage) = Storage::try_from(data.storage.clone()) {
|
||||
let Ok(report_time) = chrono::DateTime::<chrono::Local>::from_str(&req.report_time)
|
||||
else {
|
||||
tracing::error!("Failed to parse report time: {:?}", req.report_time);
|
||||
return Ok(HeartbeatResponse {});
|
||||
};
|
||||
storage.update_client(
|
||||
data.storage_token.as_ref().unwrap().clone(),
|
||||
report_time.timestamp(),
|
||||
);
|
||||
}
|
||||
|
||||
let _ = data.notifier.send(req);
|
||||
Ok(HeartbeatResponse {})
|
||||
}
|
||||
@@ -196,7 +208,11 @@ impl Session {
|
||||
|
||||
let local_configs = match storage
|
||||
.db
|
||||
.list_network_configs(user_id, Some(req.machine_id.unwrap().into()), true)
|
||||
.list_network_configs(
|
||||
user_id,
|
||||
Some(req.machine_id.unwrap().into()),
|
||||
ListNetworkProps::EnabledOnly,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(configs) => configs,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use dashmap::DashMap;
|
||||
|
||||
use crate::db::Db;
|
||||
|
||||
@@ -12,11 +12,19 @@ pub struct StorageToken {
|
||||
pub machine_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ClientInfo {
|
||||
client_url: url::Url,
|
||||
machine_id: uuid::Uuid,
|
||||
token: String,
|
||||
report_time: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StorageInner {
|
||||
// some map for indexing
|
||||
pub token_clients_map: DashMap<String, DashSet<url::Url>>,
|
||||
pub machine_client_url_map: DashMap<uuid::Uuid, DashSet<url::Url>>,
|
||||
token_clients_map: DashMap<String, DashMap<uuid::Uuid, ClientInfo>>,
|
||||
machine_client_url_map: DashMap<uuid::Uuid, ClientInfo>,
|
||||
pub db: Db,
|
||||
}
|
||||
|
||||
@@ -41,33 +49,57 @@ impl Storage {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn add_client(&self, stoken: StorageToken) {
|
||||
fn remove_mid_to_client_info_map(
|
||||
map: &DashMap<uuid::Uuid, ClientInfo>,
|
||||
machine_id: &uuid::Uuid,
|
||||
client_url: &url::Url,
|
||||
) {
|
||||
map.remove_if(&machine_id, |_, v| v.client_url == *client_url);
|
||||
}
|
||||
|
||||
fn update_mid_to_client_info_map(
|
||||
map: &DashMap<uuid::Uuid, ClientInfo>,
|
||||
client_info: &ClientInfo,
|
||||
) {
|
||||
map.entry(client_info.machine_id)
|
||||
.and_modify(|e| {
|
||||
if e.report_time < client_info.report_time {
|
||||
assert_eq!(e.machine_id, client_info.machine_id);
|
||||
*e = client_info.clone();
|
||||
}
|
||||
})
|
||||
.or_insert(client_info.clone());
|
||||
}
|
||||
|
||||
pub fn update_client(&self, stoken: StorageToken, report_time: i64) {
|
||||
let inner = self
|
||||
.0
|
||||
.token_clients_map
|
||||
.entry(stoken.token)
|
||||
.or_insert_with(DashSet::new);
|
||||
inner.insert(stoken.client_url.clone());
|
||||
.entry(stoken.token.clone())
|
||||
.or_insert_with(DashMap::new);
|
||||
|
||||
self.0
|
||||
.machine_client_url_map
|
||||
.entry(stoken.machine_id)
|
||||
.or_insert_with(DashSet::new)
|
||||
.insert(stoken.client_url.clone());
|
||||
let client_info = ClientInfo {
|
||||
client_url: stoken.client_url.clone(),
|
||||
machine_id: stoken.machine_id,
|
||||
token: stoken.token.clone(),
|
||||
report_time,
|
||||
};
|
||||
|
||||
Self::update_mid_to_client_info_map(&inner, &client_info);
|
||||
Self::update_mid_to_client_info_map(&self.0.machine_client_url_map, &client_info);
|
||||
}
|
||||
|
||||
pub fn remove_client(&self, stoken: &StorageToken) {
|
||||
self.0.token_clients_map.remove_if(&stoken.token, |_, set| {
|
||||
set.remove(&stoken.client_url);
|
||||
Self::remove_mid_to_client_info_map(set, &stoken.machine_id, &stoken.client_url);
|
||||
set.is_empty()
|
||||
});
|
||||
|
||||
self.0
|
||||
.machine_client_url_map
|
||||
.remove_if(&stoken.machine_id, |_, set| {
|
||||
set.remove(&stoken.client_url);
|
||||
set.is_empty()
|
||||
});
|
||||
Self::remove_mid_to_client_info_map(
|
||||
&self.0.machine_client_url_map,
|
||||
&stoken.machine_id,
|
||||
&stoken.client_url,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn weak_ref(&self) -> WeakRefStorage {
|
||||
@@ -78,15 +110,19 @@ impl Storage {
|
||||
self.0
|
||||
.machine_client_url_map
|
||||
.get(&machine_id)
|
||||
.map(|url| url.iter().next().map(|url| url.clone()))
|
||||
.flatten()
|
||||
.map(|info| info.client_url.clone())
|
||||
}
|
||||
|
||||
pub fn list_token_clients(&self, token: &str) -> Vec<url::Url> {
|
||||
self.0
|
||||
.token_clients_map
|
||||
.get(token)
|
||||
.map(|set| set.iter().map(|url| url.clone()).collect())
|
||||
.map(|info_map| {
|
||||
info_map
|
||||
.iter()
|
||||
.map(|info| info.value().client_url.clone())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ pub mod entity;
|
||||
|
||||
use entity::user_running_network_configs;
|
||||
use sea_orm::{
|
||||
sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait as _,
|
||||
prelude::Expr, sea_query::OnConflict, ColumnTrait as _, DatabaseConnection, DbErr, EntityTrait,
|
||||
QueryFilter as _, SqlxSqliteConnector, TransactionTrait as _,
|
||||
};
|
||||
use sea_orm_migration::MigratorTrait as _;
|
||||
@@ -14,6 +14,12 @@ use crate::migrator;
|
||||
|
||||
type UserIdInDb = i32;
|
||||
|
||||
pub enum ListNetworkProps {
|
||||
All,
|
||||
EnabledOnly,
|
||||
DisabledOnly,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Db {
|
||||
db_path: String,
|
||||
@@ -115,17 +121,51 @@ impl Db {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_network_config_state(
|
||||
&self,
|
||||
user_id: UserIdInDb,
|
||||
network_inst_id: uuid::Uuid,
|
||||
disabled: bool,
|
||||
) -> Result<entity::user_running_network_configs::Model, DbErr> {
|
||||
use entity::user_running_network_configs as urnc;
|
||||
|
||||
urnc::Entity::update_many()
|
||||
.filter(urnc::Column::UserId.eq(user_id))
|
||||
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id.to_string()))
|
||||
.col_expr(urnc::Column::Disabled, Expr::value(disabled))
|
||||
.col_expr(
|
||||
urnc::Column::UpdateTime,
|
||||
Expr::value(chrono::Local::now().fixed_offset()),
|
||||
)
|
||||
.exec(self.orm_db())
|
||||
.await?;
|
||||
|
||||
urnc::Entity::find()
|
||||
.filter(urnc::Column::UserId.eq(user_id))
|
||||
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id.to_string()))
|
||||
.one(self.orm_db())
|
||||
.await?
|
||||
.ok_or(DbErr::RecordNotFound(format!(
|
||||
"Network config not found for user {} and network instance {}",
|
||||
user_id, network_inst_id
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn list_network_configs(
|
||||
&self,
|
||||
user_id: UserIdInDb,
|
||||
device_id: Option<uuid::Uuid>,
|
||||
only_enabled: bool,
|
||||
props: ListNetworkProps,
|
||||
) -> Result<Vec<user_running_network_configs::Model>, DbErr> {
|
||||
use entity::user_running_network_configs as urnc;
|
||||
|
||||
let configs = urnc::Entity::find().filter(urnc::Column::UserId.eq(user_id));
|
||||
let configs = if only_enabled {
|
||||
configs.filter(urnc::Column::Disabled.eq(false))
|
||||
let configs = if matches!(
|
||||
props,
|
||||
ListNetworkProps::EnabledOnly | ListNetworkProps::DisabledOnly
|
||||
) {
|
||||
configs
|
||||
.filter(urnc::Column::Disabled.eq(matches!(props, ListNetworkProps::DisabledOnly)))
|
||||
} else {
|
||||
configs
|
||||
};
|
||||
@@ -140,6 +180,24 @@ impl Db {
|
||||
Ok(configs)
|
||||
}
|
||||
|
||||
pub async fn get_network_config(
|
||||
&self,
|
||||
user_id: UserIdInDb,
|
||||
device_id: &uuid::Uuid,
|
||||
network_inst_id: &String,
|
||||
) -> Result<Option<user_running_network_configs::Model>, DbErr> {
|
||||
use entity::user_running_network_configs as urnc;
|
||||
|
||||
let config = urnc::Entity::find()
|
||||
.filter(urnc::Column::UserId.eq(user_id))
|
||||
.filter(urnc::Column::DeviceId.eq(device_id.to_string()))
|
||||
.filter(urnc::Column::NetworkInstanceId.eq(network_inst_id))
|
||||
.one(self.orm_db())
|
||||
.await?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub async fn get_user_id<T: ToString>(
|
||||
&self,
|
||||
user_name: T,
|
||||
@@ -167,7 +225,7 @@ impl Db {
|
||||
mod tests {
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter as _};
|
||||
|
||||
use crate::db::{entity::user_running_network_configs, Db};
|
||||
use crate::db::{entity::user_running_network_configs, Db, ListNetworkProps};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_user_network_config_management() {
|
||||
@@ -209,7 +267,7 @@ mod tests {
|
||||
assert_ne!(result.update_time, result2.update_time);
|
||||
|
||||
assert_eq!(
|
||||
db.list_network_configs(user_id, Some(device_id), true)
|
||||
db.list_network_configs(user_id, Some(device_id), ListNetworkProps::All)
|
||||
.await
|
||||
.unwrap()
|
||||
.len(),
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate rust_i18n;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use easytier::{
|
||||
common::config::{ConfigLoader, ConsoleLoggerConfig, TomlConfigLoader},
|
||||
common::{
|
||||
config::{ConfigLoader, ConsoleLoggerConfig, FileLoggerConfig, TomlConfigLoader},
|
||||
constants::EASYTIER_VERSION,
|
||||
},
|
||||
tunnel::udp::UdpTunnelListener,
|
||||
utils::init_logger,
|
||||
utils::{init_logger, setup_panic_handler},
|
||||
};
|
||||
|
||||
mod client_manager;
|
||||
@@ -13,26 +20,97 @@ mod db;
|
||||
mod migrator;
|
||||
mod restful;
|
||||
|
||||
rust_i18n::i18n!("locales", fallback = "en");
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(name = "easytier-core", author, version = EASYTIER_VERSION , about, long_about = None)]
|
||||
struct Cli {
|
||||
#[arg(short, long, default_value = "et.db", help = t!("cli.db").to_string())]
|
||||
db: String,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("cli.console_log_level").to_string(),
|
||||
)]
|
||||
console_log_level: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("cli.file_log_level").to_string(),
|
||||
)]
|
||||
file_log_level: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
help = t!("cli.file_log_dir").to_string(),
|
||||
)]
|
||||
file_log_dir: Option<String>,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
short='c',
|
||||
default_value = "22020",
|
||||
help = t!("cli.config_server_port").to_string(),
|
||||
)]
|
||||
config_server_port: u16,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
short='p',
|
||||
default_value = "udp",
|
||||
help = t!("cli.config_server_protocol").to_string(),
|
||||
)]
|
||||
config_server_protocol: String,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
short='a',
|
||||
default_value = "11211",
|
||||
help = t!("cli.api_server_port").to_string(),
|
||||
)]
|
||||
api_server_port: u16,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
||||
rust_i18n::set_locale(&locale);
|
||||
setup_panic_handler();
|
||||
|
||||
let cli = Cli::parse();
|
||||
let config = TomlConfigLoader::default();
|
||||
config.set_console_logger_config(ConsoleLoggerConfig {
|
||||
level: Some("trace".to_string()),
|
||||
level: cli.console_log_level,
|
||||
});
|
||||
config.set_file_logger_config(FileLoggerConfig {
|
||||
dir: cli.file_log_dir,
|
||||
level: cli.file_log_level,
|
||||
file: None,
|
||||
});
|
||||
init_logger(config, false).unwrap();
|
||||
|
||||
// let db = db::Db::new(":memory:").await.unwrap();
|
||||
let db = db::Db::new("et.db").await.unwrap();
|
||||
let db = db::Db::new(cli.db).await.unwrap();
|
||||
|
||||
let listener = UdpTunnelListener::new("udp://0.0.0.0:22020".parse().unwrap());
|
||||
let listener = UdpTunnelListener::new(
|
||||
format!(
|
||||
"{}://0.0.0.0:{}",
|
||||
cli.config_server_protocol, cli.config_server_port
|
||||
)
|
||||
.parse()
|
||||
.unwrap(),
|
||||
);
|
||||
let mut mgr = client_manager::ClientManager::new(db.clone());
|
||||
mgr.serve(listener).await.unwrap();
|
||||
let mgr = Arc::new(mgr);
|
||||
|
||||
let mut restful_server =
|
||||
restful::RestfulServer::new("0.0.0.0:11211".parse().unwrap(), mgr.clone(), db)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut restful_server = restful::RestfulServer::new(
|
||||
format!("0.0.0.0:{}", cli.api_server_port).parse().unwrap(),
|
||||
mgr.clone(),
|
||||
db,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
restful_server.start().await.unwrap();
|
||||
tokio::signal::ctrl_c().await.unwrap();
|
||||
}
|
||||
|
||||
@@ -121,7 +121,9 @@ impl RestfulServer {
|
||||
return Err((StatusCode::UNAUTHORIZED, other_error("No such user").into()));
|
||||
};
|
||||
|
||||
let machines = client_mgr.list_machine_by_token(user.tokens[0].clone());
|
||||
let machines = client_mgr
|
||||
.list_machine_by_token(user.tokens[0].clone())
|
||||
.await;
|
||||
|
||||
Ok(GetSummaryJsonResp {
|
||||
device_count: machines.len() as u32,
|
||||
@@ -167,7 +169,7 @@ impl RestfulServer {
|
||||
.deflate(true)
|
||||
.gzip(true)
|
||||
.zstd(true)
|
||||
.quality(tower_http::compression::CompressionLevel::Best);
|
||||
.quality(tower_http::compression::CompressionLevel::Default);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/v1/summary", get(Self::handle_get_summary))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::{Path, Query};
|
||||
use axum::extract::Path;
|
||||
use axum::http::StatusCode;
|
||||
use axum::routing::{delete, post};
|
||||
use axum::{extract::State, routing::get, Json, Router};
|
||||
@@ -13,6 +13,7 @@ use easytier::proto::web::*;
|
||||
|
||||
use crate::client_manager::session::Session;
|
||||
use crate::client_manager::ClientManager;
|
||||
use crate::db::ListNetworkProps;
|
||||
|
||||
use super::users::AuthSession;
|
||||
use super::{
|
||||
@@ -46,13 +47,21 @@ struct ColletNetworkInfoJsonReq {
|
||||
inst_ids: Option<Vec<uuid::Uuid>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct UpdateNetworkStateJsonReq {
|
||||
disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct RemoveNetworkJsonReq {
|
||||
inst_ids: Vec<uuid::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct ListNetworkInstanceIdsJsonResp(Vec<uuid::Uuid>);
|
||||
struct ListNetworkInstanceIdsJsonResp {
|
||||
running_inst_ids: Vec<easytier::proto::common::Uuid>,
|
||||
disabled_inst_ids: Vec<easytier::proto::common::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct ListMachineItem {
|
||||
@@ -190,7 +199,7 @@ impl NetworkApi {
|
||||
auth_session: AuthSession,
|
||||
State(client_mgr): AppState,
|
||||
Path(machine_id): Path<uuid::Uuid>,
|
||||
Query(payload): Query<ColletNetworkInfoJsonReq>,
|
||||
Json(payload): Json<ColletNetworkInfoJsonReq>,
|
||||
) -> Result<Json<CollectNetworkInfoResponse>, HttpHandleError> {
|
||||
let result =
|
||||
Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
|
||||
@@ -226,10 +235,28 @@ impl NetworkApi {
|
||||
.list_network_instance(BaseController::default(), ListNetworkInstanceRequest {})
|
||||
.await
|
||||
.map_err(convert_rpc_error)?;
|
||||
Ok(
|
||||
ListNetworkInstanceIdsJsonResp(ret.inst_ids.into_iter().map(Into::into).collect())
|
||||
.into(),
|
||||
)
|
||||
|
||||
let running_inst_ids = ret.inst_ids.clone().into_iter().map(Into::into).collect();
|
||||
|
||||
// collect networks that are disabled
|
||||
let disabled_inst_ids = client_mgr
|
||||
.db()
|
||||
.list_network_configs(
|
||||
auth_session.user.unwrap().id(),
|
||||
Some(machine_id),
|
||||
ListNetworkProps::DisabledOnly,
|
||||
)
|
||||
.await
|
||||
.map_err(convert_db_error)?
|
||||
.iter()
|
||||
.filter_map(|x| x.network_instance_id.clone().try_into().ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(ListNetworkInstanceIdsJsonResp {
|
||||
running_inst_ids,
|
||||
disabled_inst_ids,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
async fn handle_remove_network_instance(
|
||||
@@ -270,7 +297,7 @@ impl NetworkApi {
|
||||
|
||||
let client_urls = DashSet::new();
|
||||
for token in tokens {
|
||||
let urls = client_mgr.list_machine_by_token(token);
|
||||
let urls = client_mgr.list_machine_by_token(token).await;
|
||||
for url in urls {
|
||||
client_urls.insert(url);
|
||||
}
|
||||
@@ -289,6 +316,54 @@ impl NetworkApi {
|
||||
Ok(Json(ListMachineJsonResp { machines }))
|
||||
}
|
||||
|
||||
async fn handle_update_network_state(
|
||||
auth_session: AuthSession,
|
||||
State(client_mgr): AppState,
|
||||
Path((machine_id, inst_id)): Path<(uuid::Uuid, Option<uuid::Uuid>)>,
|
||||
Json(payload): Json<UpdateNetworkStateJsonReq>,
|
||||
) -> Result<(), HttpHandleError> {
|
||||
let Some(inst_id) = inst_id else {
|
||||
// not implement disable all
|
||||
return Err((
|
||||
StatusCode::NOT_IMPLEMENTED,
|
||||
other_error(format!("Not implemented")).into(),
|
||||
))
|
||||
.into();
|
||||
};
|
||||
|
||||
let sess = Self::get_session_by_machine_id(&auth_session, &client_mgr, &machine_id).await?;
|
||||
let cfg = client_mgr
|
||||
.db()
|
||||
.update_network_config_state(auth_session.user.unwrap().id(), inst_id, payload.disabled)
|
||||
.await
|
||||
.map_err(convert_db_error)?;
|
||||
|
||||
let c = sess.scoped_rpc_client();
|
||||
|
||||
if payload.disabled {
|
||||
c.delete_network_instance(
|
||||
BaseController::default(),
|
||||
DeleteNetworkInstanceRequest {
|
||||
inst_ids: vec![inst_id.into()],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(convert_rpc_error)?;
|
||||
} else {
|
||||
c.run_network_instance(
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: Some(inst_id.into()),
|
||||
config: Some(serde_json::from_str(&cfg.network_config).unwrap()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(convert_rpc_error)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_get_network_config(
|
||||
auth_session: AuthSession,
|
||||
State(client_mgr): AppState,
|
||||
@@ -298,25 +373,24 @@ impl NetworkApi {
|
||||
|
||||
let db_row = client_mgr
|
||||
.db()
|
||||
.list_network_configs(auth_session.user.unwrap().id(), Some(machine_id), false)
|
||||
.get_network_config(auth_session.user.unwrap().id(), &machine_id, &inst_id)
|
||||
.await
|
||||
.map_err(convert_db_error)?
|
||||
.iter()
|
||||
.find(|x| x.network_instance_id == inst_id)
|
||||
.map(|x| x.network_config.clone())
|
||||
.ok_or((
|
||||
StatusCode::NOT_FOUND,
|
||||
other_error(format!("No such network instance: {}", inst_id)).into(),
|
||||
))?;
|
||||
|
||||
Ok(serde_json::from_str::<NetworkConfig>(&db_row)
|
||||
.map_err(|e| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
other_error(format!("Failed to parse network config: {:?}", e)).into(),
|
||||
)
|
||||
})?
|
||||
.into())
|
||||
Ok(
|
||||
serde_json::from_str::<NetworkConfig>(&db_row.network_config)
|
||||
.map_err(|e| {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
other_error(format!("Failed to parse network config: {:?}", e)).into(),
|
||||
)
|
||||
})?
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_route(&mut self) -> Router<AppStateInner> {
|
||||
@@ -332,7 +406,7 @@ impl NetworkApi {
|
||||
)
|
||||
.route(
|
||||
"/api/v1/machines/:machine-id/networks/:inst-id",
|
||||
delete(Self::handle_remove_network_instance),
|
||||
delete(Self::handle_remove_network_instance).put(Self::handle_update_network_state),
|
||||
)
|
||||
.route(
|
||||
"/api/v1/machines/:machine-id/networks/info",
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "easytier"
|
||||
description = "A full meshed p2p VPN, connecting all your devices in one network with one command."
|
||||
homepage = "https://github.com/EasyTier/EasyTier"
|
||||
repository = "https://github.com/EasyTier/EasyTier"
|
||||
version = "2.1.0"
|
||||
version = "2.1.1"
|
||||
edition = "2021"
|
||||
authors = ["kkrainbow"]
|
||||
keywords = ["vpn", "p2p", "network", "easytier"]
|
||||
|
||||
@@ -828,7 +828,7 @@ async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let locale = sys_locale::get_locale().unwrap_or_else(|| String::from("en-US"));
|
||||
rust_i18n::set_locale(&locale);
|
||||
|
||||
@@ -38,6 +38,33 @@ impl<L: TunnelListener + 'static> StandAloneServer<L> {
|
||||
&self.registry
|
||||
}
|
||||
|
||||
async fn serve_loop(
|
||||
listener: &mut L,
|
||||
inflight: Arc<AtomicU32>,
|
||||
registry: Arc<ServiceRegistry>,
|
||||
tasks: Arc<Mutex<JoinSet<()>>>,
|
||||
) -> Result<(), Error> {
|
||||
listener
|
||||
.listen()
|
||||
.await
|
||||
.with_context(|| "failed to listen")?;
|
||||
|
||||
loop {
|
||||
let tunnel = listener.accept().await?;
|
||||
let registry = registry.clone();
|
||||
let inflight_server = inflight.clone();
|
||||
inflight_server.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
let server =
|
||||
BidirectRpcManager::new().set_rx_timeout(Some(Duration::from_secs(60)));
|
||||
server.rpc_server().registry().replace_registry(®istry);
|
||||
server.run_with_tunnel(tunnel);
|
||||
server.wait().await;
|
||||
inflight_server.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve(&mut self) -> Result<(), Error> {
|
||||
let tasks = self.tasks.clone();
|
||||
let mut listener = self.listener.take().unwrap();
|
||||
@@ -45,28 +72,24 @@ impl<L: TunnelListener + 'static> StandAloneServer<L> {
|
||||
|
||||
join_joinset_background(tasks.clone(), "standalone server tasks".to_string());
|
||||
|
||||
listener
|
||||
.listen()
|
||||
.await
|
||||
.with_context(|| "failed to listen")?;
|
||||
|
||||
let inflight_server = self.inflight_server.clone();
|
||||
|
||||
self.tasks.lock().unwrap().spawn(async move {
|
||||
while let Ok(tunnel) = listener.accept().await {
|
||||
let registry = registry.clone();
|
||||
let inflight_server = inflight_server.clone();
|
||||
inflight_server.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
tasks.lock().unwrap().spawn(async move {
|
||||
let server =
|
||||
BidirectRpcManager::new().set_rx_timeout(Some(Duration::from_secs(60)));
|
||||
server.rpc_server().registry().replace_registry(®istry);
|
||||
server.run_with_tunnel(tunnel);
|
||||
server.wait().await;
|
||||
inflight_server.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
|
||||
});
|
||||
loop {
|
||||
let ret = Self::serve_loop(
|
||||
&mut listener,
|
||||
inflight_server.clone(),
|
||||
registry.clone(),
|
||||
tasks.clone(),
|
||||
)
|
||||
.await;
|
||||
if let Err(e) = ret {
|
||||
tracing::error!(?e, url = ?listener.local_url(), "serve_loop exit unexpectedly");
|
||||
println!("standalone serve_loop exit unexpectedly: {:?}", e);
|
||||
}
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
}
|
||||
panic!("standalone server listener exit");
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -287,6 +287,8 @@ async fn standalone_rpc_test() {
|
||||
server.registry().register(service, "test");
|
||||
server.serve().await.unwrap();
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
|
||||
|
||||
let mut client = StandAloneClient::new(TcpTunnelConnector::new(
|
||||
"tcp://127.0.0.1:33455".parse().unwrap(),
|
||||
));
|
||||
|
||||
@@ -33,6 +33,7 @@ impl TcpTunnelListener {
|
||||
#[async_trait]
|
||||
impl TunnelListener for TcpTunnelListener {
|
||||
async fn listen(&mut self) -> Result<(), TunnelError> {
|
||||
self.listener = None;
|
||||
let addr = check_scheme_and_get_socket_addr::<SocketAddr>(&self.addr, "tcp")?;
|
||||
|
||||
let socket2_socket = socket2::Socket::new(
|
||||
|
||||
@@ -139,12 +139,17 @@ pub fn setup_panic_handler() {
|
||||
|
||||
if let Some(payload_str) = payload_str {
|
||||
println!(
|
||||
"panic occurred: payload:{}, location: {:?}",
|
||||
"panic occurred: payload:{}, location: {:?}, backtrace: {:#?}",
|
||||
payload_str,
|
||||
info.location()
|
||||
info.location(),
|
||||
backtrace
|
||||
);
|
||||
} else {
|
||||
println!("panic occurred: location: {:?}", info.location());
|
||||
println!(
|
||||
"panic occurred: location: {:?}, backtrace: {:#?}",
|
||||
info.location(),
|
||||
backtrace
|
||||
);
|
||||
}
|
||||
println!("{}", rust_i18n::t!("core_app.panic_backtrace_save"));
|
||||
let _ = std::fs::File::create("easytier-panic.log")
|
||||
@@ -168,4 +173,4 @@ mod tests {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
|
||||
tracing::debug!("test display debug");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
161
pnpm-lock.yaml
generated
161
pnpm-lock.yaml
generated
@@ -69,6 +69,9 @@ importers:
|
||||
'@tauri-apps/cli':
|
||||
specifier: 2.1.0
|
||||
version: 2.1.0
|
||||
'@types/default-gateway':
|
||||
specifier: ^7.2.2
|
||||
version: 7.2.2
|
||||
'@types/node':
|
||||
specifier: ^22.7.4
|
||||
version: 22.8.6
|
||||
@@ -84,15 +87,18 @@ importers:
|
||||
autoprefixer:
|
||||
specifier: ^10.4.20
|
||||
version: 10.4.20(postcss@8.4.47)
|
||||
cidr-tools:
|
||||
specifier: ^11.0.2
|
||||
version: 11.0.2
|
||||
default-gateway:
|
||||
specifier: ^7.2.2
|
||||
version: 7.2.2
|
||||
eslint:
|
||||
specifier: ^9.12.0
|
||||
version: 9.14.0(jiti@2.4.0)
|
||||
eslint-plugin-format:
|
||||
specifier: ^0.1.2
|
||||
version: 0.1.2(eslint@9.14.0(jiti@2.4.0))
|
||||
internal-ip:
|
||||
specifier: ^8.0.0
|
||||
version: 8.0.0
|
||||
postcss:
|
||||
specifier: ^8.4.47
|
||||
version: 8.4.47
|
||||
@@ -905,16 +911,16 @@ packages:
|
||||
resolution: {integrity: sha512-AFbhEo10DP095/45EauinQJ5hJ3rJUmuuqltGguvc3WsvezZN+g8qNHLGWKu60FHQVizMrQY7VJ+zVlBXlQQkQ==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/message-compiler@11.0.0-beta.1':
|
||||
resolution: {integrity: sha512-yMXfN4hg/EeSdtWfmoMrwB9X4TXwkBoZlTIpNydQaW9y0tSJHGnUPRoahtkbsyACCm9leSJINLY4jQ0rK6BK0Q==}
|
||||
'@intlify/message-compiler@11.0.0-rc.1':
|
||||
resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@10.0.4':
|
||||
resolution: {integrity: sha512-ukFn0I01HsSgr3VYhYcvkTCLS7rGa0gw4A4AMpcy/A9xx/zRJy7PS2BElMXLwUazVFMAr5zuiTk3MQeoeGXaJg==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/shared@11.0.0-beta.1':
|
||||
resolution: {integrity: sha512-Md/4T/QOx7wZ7zqVzSsMx2M/9Mx/1nsgsjXS5SFIowFKydqUhMz7K+y7pMFh781aNYz+rGXYwad8E9/+InK9SA==}
|
||||
'@intlify/shared@11.0.0-rc.1':
|
||||
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
|
||||
engines: {node: '>= 16'}
|
||||
|
||||
'@intlify/unplugin-vue-i18n@5.2.0':
|
||||
@@ -1353,6 +1359,9 @@ packages:
|
||||
'@types/debug@4.1.12':
|
||||
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
|
||||
|
||||
'@types/default-gateway@7.2.2':
|
||||
resolution: {integrity: sha512-35C93fYQlnLKLASkMPoxRvok4fENwB3By9clRLd2I/08n/XRl0pCdf7EB17K5oMMwZu8NBYA8i66jH5r/LYBKA==}
|
||||
|
||||
'@types/estree@1.0.6':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
@@ -1916,13 +1925,9 @@ packages:
|
||||
resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
cidr-regex@4.0.3:
|
||||
resolution: {integrity: sha512-HOwDIy/rhKeMf6uOzxtv7FAbrz8zPjmVKfSpM+U7/bNBXC5rtOyr758jxcptiSx6ZZn5LOhPJT5WWxPAGDV8dw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
cidr-tools@6.4.2:
|
||||
resolution: {integrity: sha512-KZC8t2ipCqU2M+ISmTxRDGu9bku5MRU3V1cWyGEFJTZEzRhGvBJvVsbpZO5UAu12fExRFihtYGXAlgFFpmK9jw==}
|
||||
engines: {node: '>=16'}
|
||||
cidr-tools@11.0.2:
|
||||
resolution: {integrity: sha512-OLeM9EOXybbhMsGGBNRLCMjn8e+wFOXARIShF/sZwmJLsxWywqfE0By4BMftT6BFWpbcETWpW7TfM2KGCtrZDg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
clean-regexp@1.0.0:
|
||||
resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==}
|
||||
@@ -1932,10 +1937,6 @@ packages:
|
||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
clone-regexp@3.0.0:
|
||||
resolution: {integrity: sha512-ujdnoq2Kxb8s3ItNBtnYeXdm07FcU0u8ARAT1lQ2YdMwQC+cdiXX8KoqMVuglztILivceTtp4ivqGSmEmhBUJw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@@ -1967,10 +1968,6 @@ packages:
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
convert-hrtime@5.0.0:
|
||||
resolution: {integrity: sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
convert-source-map@2.0.0:
|
||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||
|
||||
@@ -2436,10 +2433,6 @@ packages:
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
function-timeout@0.1.1:
|
||||
resolution: {integrity: sha512-0NVVC0TaP7dSTvn1yMiy6d6Q8gifzbvQafO46RtLG/kHJUBNd+pVRGOBoK44wNBvtSPUJRfdVvkFdD3p0xvyZg==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
gensync@1.0.0-beta.2:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -2554,21 +2547,13 @@ packages:
|
||||
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
internal-ip@8.0.0:
|
||||
resolution: {integrity: sha512-e6c3zxr9COnnc29PIz9LffmALOt0XhIJdR7f83DyHcQksL3B40KGmU3Sr1lrHja3i7Zyqo+AbwKZ+nZiMvg/OA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
ip-bigint@7.3.0:
|
||||
resolution: {integrity: sha512-2qVAe0Q9+Y+5nGvmogwK9y4kefD5Ks5l/IG0Jo1lhU9gIF34jifhqrwXwzkIl+LC594Q6SyAlngs4p890xsXVw==}
|
||||
engines: {node: '>=16'}
|
||||
ip-bigint@8.2.0:
|
||||
resolution: {integrity: sha512-46EAEKzGNxojH5JaGEeCix49tL4h1W8ia5mhogZ68HroVAfyLj1E+SFFid4GuyK0mdIKjwcAITLqwg1wlkx2iQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
ip-num@1.5.1:
|
||||
resolution: {integrity: sha512-QziFxgxq3mjIf5CuwlzXFYscHxgLqdEdJKRo2UJ5GurL5zrSRMzT/O+nK0ABimoFH8MWF8YwIiwECYsHc1LpUQ==}
|
||||
|
||||
ip-regex@5.0.0:
|
||||
resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
||||
@@ -2610,18 +2595,10 @@ packages:
|
||||
engines: {node: '>=14.16'}
|
||||
hasBin: true
|
||||
|
||||
is-ip@5.0.1:
|
||||
resolution: {integrity: sha512-FCsGHdlrOnZQcp0+XT5a+pYowf33itBalCl+7ovNXC/7o5BhIpG14M3OrpPPdBSIQJCm+0M5+9mO7S9VVTTCFw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
is-number@7.0.0:
|
||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
||||
engines: {node: '>=0.12.0'}
|
||||
|
||||
is-regexp@3.1.0:
|
||||
resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-stream@3.0.0:
|
||||
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -3023,10 +3000,6 @@ packages:
|
||||
oxc-resolver@2.0.1:
|
||||
resolution: {integrity: sha512-xEbYdEGwafn+Y2GTyW0BGC3iIjJZXl+fxrIkyheew5mZrDODmPXJf2qwsa1ocBeVUC51g9e835vNZ9tRR5fYCg==}
|
||||
|
||||
p-event@5.0.1:
|
||||
resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
p-limit@2.3.0:
|
||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3043,10 +3016,6 @@ packages:
|
||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
p-timeout@5.1.0:
|
||||
resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
p-try@2.2.0:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3402,9 +3371,6 @@ packages:
|
||||
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
|
||||
engines: {node: '>=0.6.19'}
|
||||
|
||||
string-natural-compare@3.0.1:
|
||||
resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==}
|
||||
|
||||
string-width@4.2.3:
|
||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -3445,10 +3411,6 @@ packages:
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
|
||||
super-regex@0.2.0:
|
||||
resolution: {integrity: sha512-WZzIx3rC1CvbMDloLsVw0lkZVKJWbrkJ0k1ghKFmcnPrW1+jWbgTkTEWVtD9lMdmI4jZEz40+naBxl1dCUhXXw==}
|
||||
engines: {node: '>=14.16'}
|
||||
|
||||
superjson@2.2.1:
|
||||
resolution: {integrity: sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -3500,10 +3462,6 @@ packages:
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
time-span@5.1.0:
|
||||
resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
tinyexec@0.3.1:
|
||||
resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==}
|
||||
|
||||
@@ -4420,8 +4378,8 @@ snapshots:
|
||||
|
||||
'@intlify/bundle-utils@9.0.0(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))':
|
||||
dependencies:
|
||||
'@intlify/message-compiler': 11.0.0-beta.1
|
||||
'@intlify/shared': 11.0.0-beta.1
|
||||
'@intlify/message-compiler': 11.0.0-rc.1
|
||||
'@intlify/shared': 11.0.0-rc.1
|
||||
acorn: 8.14.0
|
||||
escodegen: 2.1.0
|
||||
estree-walker: 2.0.2
|
||||
@@ -4442,21 +4400,21 @@ snapshots:
|
||||
'@intlify/shared': 10.0.4
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@intlify/message-compiler@11.0.0-beta.1':
|
||||
'@intlify/message-compiler@11.0.0-rc.1':
|
||||
dependencies:
|
||||
'@intlify/shared': 11.0.0-beta.1
|
||||
'@intlify/shared': 11.0.0-rc.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@intlify/shared@10.0.4': {}
|
||||
|
||||
'@intlify/shared@11.0.0-beta.1': {}
|
||||
'@intlify/shared@11.0.0-rc.1': {}
|
||||
|
||||
'@intlify/unplugin-vue-i18n@5.2.0(@vue/compiler-dom@3.5.12)(eslint@9.14.0(jiti@2.4.0))(rollup@4.24.3)(typescript@5.6.3)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.4.1(eslint@9.14.0(jiti@2.4.0))
|
||||
'@intlify/bundle-utils': 9.0.0(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))
|
||||
'@intlify/shared': 11.0.0-beta.1
|
||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.0.0-beta.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))
|
||||
'@intlify/shared': 11.0.0-rc.1
|
||||
'@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.0.0-rc.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))
|
||||
'@rollup/pluginutils': 5.1.3(rollup@4.24.3)
|
||||
'@typescript-eslint/scope-manager': 7.18.0
|
||||
'@typescript-eslint/typescript-estree': 7.18.0(typescript@5.6.3)
|
||||
@@ -4479,11 +4437,11 @@ snapshots:
|
||||
- typescript
|
||||
- webpack-sources
|
||||
|
||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.0.0-beta.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))':
|
||||
'@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.0.0-rc.1)(@vue/compiler-dom@3.5.12)(vue-i18n@10.0.4(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.2
|
||||
optionalDependencies:
|
||||
'@intlify/shared': 11.0.0-beta.1
|
||||
'@intlify/shared': 11.0.0-rc.1
|
||||
'@vue/compiler-dom': 3.5.12
|
||||
vue: 3.5.12(typescript@5.6.3)
|
||||
vue-i18n: 10.0.4(vue@3.5.12(typescript@5.6.3))
|
||||
@@ -4865,6 +4823,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/ms': 0.7.34
|
||||
|
||||
'@types/default-gateway@7.2.2': {}
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
@@ -5649,16 +5609,9 @@ snapshots:
|
||||
|
||||
ci-info@4.0.0: {}
|
||||
|
||||
cidr-regex@4.0.3:
|
||||
cidr-tools@11.0.2:
|
||||
dependencies:
|
||||
ip-regex: 5.0.0
|
||||
|
||||
cidr-tools@6.4.2:
|
||||
dependencies:
|
||||
cidr-regex: 4.0.3
|
||||
ip-bigint: 7.3.0
|
||||
ip-regex: 5.0.0
|
||||
string-natural-compare: 3.0.1
|
||||
ip-bigint: 8.2.0
|
||||
|
||||
clean-regexp@1.0.0:
|
||||
dependencies:
|
||||
@@ -5670,10 +5623,6 @@ snapshots:
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 7.0.0
|
||||
|
||||
clone-regexp@3.0.0:
|
||||
dependencies:
|
||||
is-regexp: 3.1.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
@@ -5696,8 +5645,6 @@ snapshots:
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
convert-hrtime@5.0.0: {}
|
||||
|
||||
convert-source-map@2.0.0: {}
|
||||
|
||||
copy-anything@3.0.5:
|
||||
@@ -6278,8 +6225,6 @@ snapshots:
|
||||
|
||||
function-bind@1.1.2: {}
|
||||
|
||||
function-timeout@0.1.1: {}
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
@@ -6381,19 +6326,10 @@ snapshots:
|
||||
|
||||
indent-string@4.0.0: {}
|
||||
|
||||
internal-ip@8.0.0:
|
||||
dependencies:
|
||||
cidr-tools: 6.4.2
|
||||
default-gateway: 7.2.2
|
||||
is-ip: 5.0.1
|
||||
p-event: 5.0.1
|
||||
|
||||
ip-bigint@7.3.0: {}
|
||||
ip-bigint@8.2.0: {}
|
||||
|
||||
ip-num@1.5.1: {}
|
||||
|
||||
ip-regex@5.0.0: {}
|
||||
|
||||
is-arrayish@0.2.1: {}
|
||||
|
||||
is-binary-path@2.1.0:
|
||||
@@ -6424,15 +6360,8 @@ snapshots:
|
||||
dependencies:
|
||||
is-docker: 3.0.0
|
||||
|
||||
is-ip@5.0.1:
|
||||
dependencies:
|
||||
ip-regex: 5.0.0
|
||||
super-regex: 0.2.0
|
||||
|
||||
is-number@7.0.0: {}
|
||||
|
||||
is-regexp@3.1.0: {}
|
||||
|
||||
is-stream@3.0.0: {}
|
||||
|
||||
is-what@4.1.16: {}
|
||||
@@ -6992,10 +6921,6 @@ snapshots:
|
||||
'@oxc-resolver/binding-win32-arm64-msvc': 2.0.1
|
||||
'@oxc-resolver/binding-win32-x64-msvc': 2.0.1
|
||||
|
||||
p-event@5.0.1:
|
||||
dependencies:
|
||||
p-timeout: 5.1.0
|
||||
|
||||
p-limit@2.3.0:
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
@@ -7012,8 +6937,6 @@ snapshots:
|
||||
dependencies:
|
||||
p-limit: 3.1.0
|
||||
|
||||
p-timeout@5.1.0: {}
|
||||
|
||||
p-try@2.2.0: {}
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
@@ -7332,8 +7255,6 @@ snapshots:
|
||||
|
||||
string-argv@0.3.2: {}
|
||||
|
||||
string-natural-compare@3.0.1: {}
|
||||
|
||||
string-width@4.2.3:
|
||||
dependencies:
|
||||
emoji-regex: 8.0.0
|
||||
@@ -7378,12 +7299,6 @@ snapshots:
|
||||
pirates: 4.0.6
|
||||
ts-interface-checker: 0.1.13
|
||||
|
||||
super-regex@0.2.0:
|
||||
dependencies:
|
||||
clone-regexp: 3.0.0
|
||||
function-timeout: 0.1.1
|
||||
time-span: 5.1.0
|
||||
|
||||
superjson@2.2.1:
|
||||
dependencies:
|
||||
copy-anything: 3.0.5
|
||||
@@ -7452,10 +7367,6 @@ snapshots:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
time-span@5.1.0:
|
||||
dependencies:
|
||||
convert-hrtime: 5.0.0
|
||||
|
||||
tinyexec@0.3.1: {}
|
||||
|
||||
to-regex-range@5.0.1:
|
||||
|
||||
@@ -89,7 +89,7 @@ class TauriVpnService : VpnService() {
|
||||
|
||||
for (route in routes) {
|
||||
val ipParts = route.split("/")
|
||||
if (ipParts.size != 2) throw IllegalArgumentException("Invalid IP addr string")
|
||||
if (ipParts.size != 2) throw IllegalArgumentException("Invalid route cidr string")
|
||||
builder.addRoute(ipParts[0], ipParts[1].toInt())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const COMMANDS: &[&str] = &[
|
||||
"prepare_vpn",
|
||||
"start_vpn",
|
||||
"stop_vpn",
|
||||
"register_listener",
|
||||
"registerListener",
|
||||
];
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-registerListener"
|
||||
description = "Enables the registerListener command without any pre-configured scope."
|
||||
commands.allow = ["registerListener"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-registerListener"
|
||||
description = "Denies the registerListener command without any pre-configured scope."
|
||||
commands.deny = ["registerListener"]
|
||||
@@ -69,6 +69,32 @@ Denies the prepare_vpn command without any pre-configured scope.
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpnservice:allow-registerListener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the registerListener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpnservice:deny-registerListener`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the registerListener command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`vpnservice:allow-register-listener`
|
||||
|
||||
</td>
|
||||
|
||||
@@ -314,6 +314,16 @@
|
||||
"type": "string",
|
||||
"const": "deny-prepare-vpn"
|
||||
},
|
||||
{
|
||||
"description": "Enables the registerListener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-registerListener"
|
||||
},
|
||||
{
|
||||
"description": "Denies the registerListener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-registerListener"
|
||||
},
|
||||
{
|
||||
"description": "Enables the register_listener command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
|
||||
Reference in New Issue
Block a user