🌈 style: eslint lint

This commit is contained in:
m1m1sha 2024-05-06 11:08:51 +08:00
parent 6f75dd72b9
commit c66939249f
16 changed files with 602 additions and 568 deletions

View File

@ -32,7 +32,7 @@ settings: Settings
exchange_language: 切换中文 exchange_language: 切换中文
exit: Exit exit: Exit
chips_placeholder: "e.g: {0}, press Enter to add" chips_placeholder: 'e.g: {0}, press Enter to add'
off_text: Press to disable off_text: Press to disable
on_text: Press to enable on_text: Press to enable
show_config: Show Config show_config: Show Config

View File

@ -33,6 +33,7 @@
"eslint-plugin-format": "^0.1.1", "eslint-plugin-format": "^0.1.1",
"naive-ui": "^2.38.2", "naive-ui": "^2.38.2",
"postcss": "^8.4.38", "postcss": "^8.4.38",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.3", "tailwindcss": "^3.4.3",
"typescript": "^5.4.5", "typescript": "^5.4.5",
"unplugin-auto-import": "^0.17.5", "unplugin-auto-import": "^0.17.5",
@ -45,7 +46,6 @@
"vite": "^5.2.11", "vite": "^5.2.11",
"vite-plugin-vue-devtools": "^7.1.3", "vite-plugin-vue-devtools": "^7.1.3",
"vite-plugin-vue-layouts": "^0.11.0", "vite-plugin-vue-layouts": "^0.11.0",
"prettier": "^3.2.5",
"vue-i18n": "^9.13.1", "vue-i18n": "^9.13.1",
"vue-tsc": "^2.0.16" "vue-tsc": "^2.0.16"
} }

View File

@ -1,3 +1,3 @@
<template> <template>
<RouterView /> <RouterView />
</template> </template>

View File

@ -1,39 +1,37 @@
<script setup lang="ts"> <script setup lang="ts">
import InputGroup from "primevue/inputgroup"; import InputGroup from 'primevue/inputgroup'
import InputGroupAddon from "primevue/inputgroupaddon"; import InputGroupAddon from 'primevue/inputgroupaddon'
import { i18n } from "~/modules/i18n"; import { i18n } from '~/modules/i18n'
import { NetworkingMethod } from "~/types/network"; import { NetworkingMethod } from '~/types/network'
const props = defineProps<{
configInvalid?: boolean
instanceId?: string
}>()
defineEmits(['runNetwork'])
const networking_methods = ref([ const networking_methods = ref([
{ value: NetworkingMethod.PublicServer, label: i18n.global.t('public_server') }, { value: NetworkingMethod.PublicServer, label: i18n.global.t('public_server') },
{ value: NetworkingMethod.Manual, label: i18n.global.t('manual') }, { value: NetworkingMethod.Manual, label: i18n.global.t('manual') },
{ value: NetworkingMethod.Standalone, label: i18n.global.t('standalone') }, { value: NetworkingMethod.Standalone, label: i18n.global.t('standalone') },
]); ])
const props = defineProps<{ const networkStore = useNetworkStore()
configInvalid?: boolean,
instanceId?: string,
}>()
defineEmits(["runNetwork"]);
const networkStore = useNetworkStore();
const curNetwork = computed(() => { const curNetwork = computed(() => {
if (props.instanceId) { if (props.instanceId) {
console.log("instanceId", props.instanceId); // console.log('instanceId', props.instanceId)
const c = networkStore.networkList.find(n => n.instance_id == props.instanceId); const c = networkStore.networkList.find(n => n.instance_id === props.instanceId)
if (c != undefined) { if (c !== undefined)
return c; return c
}
} }
return networkStore.curNetwork; return networkStore.curNetwork
}); })
const presetPublicServers = [ const presetPublicServers = [
"tcp://easytier.public.kkrainbow.top:11010", 'tcp://easytier.public.kkrainbow.top:11010',
]; ]
</script> </script>
<template> <template>
@ -41,9 +39,7 @@ const presetPublicServers = [
<div class="flex flex-column"> <div class="flex flex-column">
<div class="w-10/12 max-w-fit self-center "> <div class="w-10/12 max-w-fit self-center ">
<Panel header="Basic Settings"> <Panel header="Basic Settings">
<div class="flex flex-column gap-y-2"> <div class="flex flex-column gap-y-2">
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="virtual_ip">{{ $t('virtual_ipv4') }}</label> <label for="virtual_ip">{{ $t('virtual_ipv4') }}</label>
@ -63,8 +59,10 @@ const presetPublicServers = [
</div> </div>
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="network_secret">{{ $t('network_secret') }}</label> <label for="network_secret">{{ $t('network_secret') }}</label>
<InputText id="network_secret" v-model="curNetwork.network_secret" <InputText
aria-describedby=" network_secret-help" /> id="network_secret" v-model="curNetwork.network_secret"
aria-describedby=" network_secret-help"
/>
</div> </div>
</div> </div>
@ -72,15 +70,21 @@ const presetPublicServers = [
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="nm">{{ $t('networking_method') }}</label> <label for="nm">{{ $t('networking_method') }}</label>
<div class="items-center flex flex-row p-fluid gap-x-1"> <div class="items-center flex flex-row p-fluid gap-x-1">
<Dropdown v-model="curNetwork.networking_method" :options="networking_methods" optionLabel="label" <Dropdown
optionValue="value" placeholder="Select Method" class="" /> v-model="curNetwork.networking_method" :options="networking_methods" option-label="label"
<Chips id="chips" v-model="curNetwork.peer_urls" option-value="value" placeholder="Select Method" class=""
:placeholder="$t('chips_placeholder', ['tcp://8.8.8.8:11010'])" separator=" " class="grow" />
v-if="curNetwork.networking_method == NetworkingMethod.Manual" /> <Chips
v-if="curNetwork.networking_method === NetworkingMethod.Manual" id="chips"
v-model="curNetwork.peer_urls" :placeholder="$t('chips_placeholder', ['tcp://8.8.8.8:11010'])"
separator=" " class="grow"
/>
<Dropdown :editable="true" v-model="curNetwork.public_server_url" class="grow" <Dropdown
v-if="curNetwork.networking_method === NetworkingMethod.PublicServer"
v-model="curNetwork.public_server_url" :editable="true" class="grow"
:options="presetPublicServers" :options="presetPublicServers"
v-if="curNetwork.networking_method == NetworkingMethod.PublicServer" /> />
</div> </div>
</div> </div>
</div> </div>
@ -88,8 +92,10 @@ const presetPublicServers = [
<div class="flex flex-row gap-x-9 flex-wrap w-full"> <div class="flex flex-row gap-x-9 flex-wrap w-full">
<div class="flex flex-column gap-2 grow p-fluid"> <div class="flex flex-column gap-2 grow p-fluid">
<label for="username">{{ $t('proxy_cidrs') }}</label> <label for="username">{{ $t('proxy_cidrs') }}</label>
<Chips id="chips" v-model="curNetwork.proxy_cidrs" <Chips
:placeholder="$t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full" /> id="chips" v-model="curNetwork.proxy_cidrs"
:placeholder="$t('chips_placeholder', ['10.0.0.0/24'])" separator=" " class="w-full"
/>
</div> </div>
</div> </div>
@ -97,24 +103,29 @@ const presetPublicServers = [
<div class="flex flex-column gap-2 grow"> <div class="flex flex-column gap-2 grow">
<label for="username">VPN Portal</label> <label for="username">VPN Portal</label>
<div class="items-center flex flex-row gap-x-4"> <div class="items-center flex flex-row gap-x-4">
<ToggleButton onIcon="pi pi-check" offIcon="pi pi-times" v-model="curNetwork.enable_vpn_portal" <ToggleButton
:onLabel="$t('off_text')" :offLabel="$t('on_text')" /> v-model="curNetwork.enable_vpn_portal" on-icon="pi pi-check" off-icon="pi pi-times"
<div class="grow" v-if="curNetwork.enable_vpn_portal"> :on-label="$t('off_text')" :off-label="$t('on_text')"
/>
<div v-if="curNetwork.enable_vpn_portal" class="grow">
<InputGroup> <InputGroup>
<InputText :placeholder="$t('vpn_portal_client_network')" <InputText
v-model="curNetwork.vpn_portal_client_network_addr" /> v-model="curNetwork.vpn_portal_client_network_addr"
:placeholder="$t('vpn_portal_client_network')"
/>
<InputGroupAddon> <InputGroupAddon>
<span>/{{ curNetwork.vpn_portal_client_network_len }}</span> <span>/{{ curNetwork.vpn_portal_client_network_len }}</span>
</InputGroupAddon> </InputGroupAddon>
</InputGroup> </InputGroup>
</div> </div>
<InputNumber :placeholder="$t('vpn_portal_listen_port')" class="" v-if="curNetwork.enable_vpn_portal" <InputNumber
:format="false" v-model="curNetwork.vpn_portal_listne_port" :min="0" :max="65535" /> v-if="curNetwork.enable_vpn_portal" v-model="curNetwork.vpn_portal_listne_port"
:placeholder="$t('vpn_portal_listen_port')" class="" :format="false" :min="0" :max="65535"
/>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</Panel> </Panel>
<Divider /> <Divider />
@ -124,17 +135,20 @@ const presetPublicServers = [
<div class="flex flex-row gap-x-9 flex-wrap w-full"> <div class="flex flex-row gap-x-9 flex-wrap w-full">
<div class="flex flex-column gap-2 grow p-fluid"> <div class="flex flex-column gap-2 grow p-fluid">
<label for="listener_urls">{{ $t('listener_urls') }}</label> <label for="listener_urls">{{ $t('listener_urls') }}</label>
<Chips id="listener_urls" v-model="curNetwork.listener_urls" <Chips
:placeholder="$t('chips_placeholder', ['tcp://1.1.1.1:11010'])" separator=" " class="w-full" /> id="listener_urls" v-model="curNetwork.listener_urls"
:placeholder="$t('chips_placeholder', ['tcp://1.1.1.1:11010'])" separator=" " class="w-full"
/>
</div> </div>
</div> </div>
<div class="flex flex-row gap-x-9 flex-wrap"> <div class="flex flex-row gap-x-9 flex-wrap">
<div class="flex flex-column gap-2 basis-5/12 grow"> <div class="flex flex-column gap-2 basis-5/12 grow">
<label for="rpc_port">{{ $t('rpc_port') }}</label> <label for="rpc_port">{{ $t('rpc_port') }}</label>
<InputNumber id="rpc_port" v-model="curNetwork.rpc_port" aria-describedby="username-help" <InputNumber
:format="false" :min="0" :max="65535" /> id="rpc_port" v-model="curNetwork.rpc_port" aria-describedby="username-help"
:format="false" :min="0" :max="65535"
/>
</div> </div>
</div> </div>
</div> </div>
@ -142,10 +156,11 @@ const presetPublicServers = [
<Divider /> <Divider />
<div class="flex pt-4 justify-content-center"> <div class="flex pt-4 justify-content-center">
<Button :label="$t('run_network')" icon="pi pi-arrow-right" iconPos="right" <Button
@click="$emit('runNetwork', curNetwork)" :disabled="configInvalid" /> :label="$t('run_network')" icon="pi pi-arrow-right" icon-pos="right" :disabled="configInvalid"
@click="$emit('runNetwork', curNetwork)"
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,354 +1,363 @@
<script setup lang="ts"> <script setup lang="ts">
const networkStore = useNetworkStore();
const props = defineProps<{ const props = defineProps<{
instanceId?: string, instanceId?: string
}>() }>()
const networkStore = useNetworkStore()
const curNetwork = computed(() => { const curNetwork = computed(() => {
if (props.instanceId) { if (props.instanceId) {
console.log("instanceId", props.instanceId); // console.log('instanceId', props.instanceId)
const c = networkStore.networkList.find(n => n.instance_id == props.instanceId); const c = networkStore.networkList.find(n => n.instance_id === props.instanceId)
if (c != undefined) { if (c !== undefined)
return c; return c
} }
}
return networkStore.curNetwork; return networkStore.curNetwork
}); })
let curNetworkInst = computed(() => { const curNetworkInst = computed(() => {
return networkStore.networkInstances.find(n => n.instance_id == curNetwork.value.instance_id); return networkStore.networkInstances.find(n => n.instance_id === curNetwork.value.instance_id)
}); })
let peerRouteInfos = computed(() => { const peerRouteInfos = computed(() => {
if (curNetworkInst.value) { if (curNetworkInst.value)
return curNetworkInst.value.detail.peer_route_pairs; return curNetworkInst.value.detail.peer_route_pairs
}
return [];
});
let routeCost = (info: any) => { return []
if (info.route) { })
const cost = info.route.cost;
return cost == 1 ? "p2p" : `relay(${cost})`
}
return '?';
};
function resolveObjPath(path: string, obj = self, separator = '.') { function routeCost(info: any) {
var properties = Array.isArray(path) ? path : path.split(separator) if (info.route) {
return properties.reduce((prev, curr) => prev?.[curr], obj) const cost = info.route.cost
return cost === 1 ? 'p2p' : `relay(${cost})`
}
return '?'
} }
let statsCommon = (info: any, field: string) => { function resolveObjPath(path: string, obj = globalThis, separator = '.') {
if (!info.peer) { const properties = Array.isArray(path) ? path : path.split(separator)
return undefined; return properties.reduce((prev, curr) => prev?.[curr], obj)
} }
let conns = info.peer.conns;
return conns.reduce((acc: number, conn: any) => { function statsCommon(info: any, field: string) {
return acc + resolveObjPath(field, conn); if (!info.peer)
}, 0); return undefined
};
const conns = info.peer.conns
return conns.reduce((acc: number, conn: any) => {
return acc + resolveObjPath(field, conn)
}, 0)
}
function humanFileSize(bytes: number, si = false, dp = 1) { function humanFileSize(bytes: number, si = false, dp = 1) {
const thresh = si ? 1000 : 1024; const thresh = si ? 1000 : 1024
if (Math.abs(bytes) < thresh) { if (Math.abs(bytes) < thresh)
return bytes + ' B'; return `${bytes} B`
}
const units = si const units = si
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
let u = -1; let u = -1
const r = 10 ** dp; const r = 10 ** dp
do { do {
bytes /= thresh; bytes /= thresh
++u; ++u
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)
return `${bytes.toFixed(dp)} ${units[u]}`
return bytes.toFixed(dp) + ' ' + units[u];
} }
let latencyMs = (info: any) => { function latencyMs(info: any) {
let lat_us_sum = statsCommon(info, 'stats.latency_us'); const lat_us_sum = statsCommon(info, 'stats.latency_us')
return lat_us_sum ? `${lat_us_sum / 1000 / info.peer.conns.length}ms` : ''; return lat_us_sum ? `${lat_us_sum / 1000 / info.peer.conns.length}ms` : ''
};
let txBytes = (info: any) => {
let tx = statsCommon(info, 'stats.tx_bytes');
return tx ? humanFileSize(tx) : '';
} }
let rxBytes = (info: any) => { function txBytes(info: any) {
let rx = statsCommon(info, 'stats.rx_bytes'); const tx = statsCommon(info, 'stats.tx_bytes')
return rx ? humanFileSize(rx) : ''; return tx ? humanFileSize(tx) : ''
} }
let lossRate = (info: any) => { function rxBytes(info: any) {
let lossRate = statsCommon(info, 'loss_rate'); const rx = statsCommon(info, 'stats.rx_bytes')
return lossRate != undefined ? `${Math.round(lossRate * 100)}%` : ''; return rx ? humanFileSize(rx) : ''
}
function lossRate(info: any) {
const lossRate = statsCommon(info, 'loss_rate')
return lossRate !== undefined ? `${Math.round(lossRate * 100)}%` : ''
} }
const myNodeInfo = computed(() => { const myNodeInfo = computed(() => {
if (!curNetworkInst.value) { if (!curNetworkInst.value)
return {}; return {}
}
return curNetworkInst.value.detail?.my_node_info; return curNetworkInst.value.detail?.my_node_info
}); })
interface Chip { interface Chip {
label: string; label: string
icon: string; icon: string
} }
let myNodeInfoChips = computed(() => { const myNodeInfoChips = computed(() => {
if (!curNetworkInst.value) { if (!curNetworkInst.value)
return []; return []
const chips: Array<Chip> = []
const my_node_info = curNetworkInst.value.detail?.my_node_info
if (!my_node_info)
return chips
// local ipv4s
const local_ipv4s = my_node_info.ips?.interface_ipv4s
for (const [idx, ip] of local_ipv4s?.entries()) {
chips.push({
label: `Local IPv4 ${idx}: ${ip}`,
icon: '',
} as Chip)
}
// local ipv6s
const local_ipv6s = my_node_info.ips?.interface_ipv6s
for (const [idx, ip] of local_ipv6s?.entries()) {
chips.push({
label: `Local IPv6 ${idx}: ${ip}`,
icon: '',
} as Chip)
}
// public ip
const public_ip = my_node_info.ips?.public_ipv4
if (public_ip) {
chips.push({
label: `Public IP: ${public_ip}`,
icon: '',
} as Chip)
}
// listeners:
const listeners = my_node_info.listeners
for (const [idx, listener] of listeners?.entries()) {
chips.push({
label: `Listener ${idx}: ${listener}`,
icon: '',
} as Chip)
}
// udp nat type
enum NatType {
// has NAT; but own a single public IP, port is not changed
Unknown = 0,
OpenInternet = 1,
NoPAT = 2,
FullCone = 3,
Restricted = 4,
PortRestricted = 5,
Symmetric = 6,
SymUdpFirewall = 7,
};
const udpNatType: NatType = my_node_info.stun_info?.udp_nat_type
if (udpNatType !== undefined) {
const udpNatTypeStrMap = {
[NatType.Unknown]: 'Unknown',
[NatType.OpenInternet]: 'Open Internet',
[NatType.NoPAT]: 'No PAT',
[NatType.FullCone]: 'Full Cone',
[NatType.Restricted]: 'Restricted',
[NatType.PortRestricted]: 'Port Restricted',
[NatType.Symmetric]: 'Symmetric',
[NatType.SymUdpFirewall]: 'Symmetric UDP Firewall',
} }
let chips: Array<Chip> = []; chips.push({
let my_node_info = curNetworkInst.value.detail?.my_node_info; label: `UDP NAT Type: ${udpNatTypeStrMap[udpNatType]}`,
if (!my_node_info) { icon: '',
return chips; } as Chip)
} }
// local ipv4s return chips
let local_ipv4s = my_node_info.ips?.interface_ipv4s; })
for (let [idx, ip] of local_ipv4s?.entries()) {
chips.push({
label: `Local IPv4 ${idx}: ${ip}`,
icon: '',
} as Chip);
}
// local ipv6s function globalSumCommon(field: string) {
let local_ipv6s = my_node_info.ips?.interface_ipv6s; let sum = 0
for (let [idx, ip] of local_ipv6s?.entries()) { if (!peerRouteInfos.value)
chips.push({ return sum
label: `Local IPv6 ${idx}: ${ip}`,
icon: '',
} as Chip);
}
// public ip for (const info of peerRouteInfos.value) {
let public_ip = my_node_info.ips?.public_ipv4; const tx = statsCommon(info, field)
if (public_ip) { if (tx)
chips.push({ sum += tx
label: `Public IP: ${public_ip}`, }
icon: '', return sum
} as Chip);
}
// listeners:
let listeners = my_node_info.listeners;
for (let [idx, listener] of listeners?.entries()) {
chips.push({
label: `Listener ${idx}: ${listener}`,
icon: '',
} as Chip);
}
// udp nat type
enum NatType {
// has NAT; but own a single public IP, port is not changed
Unknown = 0,
OpenInternet = 1,
NoPAT = 2,
FullCone = 3,
Restricted = 4,
PortRestricted = 5,
Symmetric = 6,
SymUdpFirewall = 7,
};
let udpNatType: NatType = my_node_info.stun_info?.udp_nat_type;
if (udpNatType != undefined) {
let udpNatTypeStrMap = {
[NatType.Unknown]: 'Unknown',
[NatType.OpenInternet]: 'Open Internet',
[NatType.NoPAT]: 'No PAT',
[NatType.FullCone]: 'Full Cone',
[NatType.Restricted]: 'Restricted',
[NatType.PortRestricted]: 'Port Restricted',
[NatType.Symmetric]: 'Symmetric',
[NatType.SymUdpFirewall]: 'Symmetric UDP Firewall',
};
chips.push({
label: `UDP NAT Type: ${udpNatTypeStrMap[udpNatType]}`,
icon: '',
} as Chip);
}
return chips;
});
const globalSumCommon = (field: string) => {
let sum = 0;
if (!peerRouteInfos.value) {
return sum;
}
for (let info of peerRouteInfos.value) {
let tx = statsCommon(info, field);
if (tx) {
sum += tx;
}
}
return sum;
};
const txGlobalSum = () => {
return globalSumCommon('stats.tx_bytes');
};
const rxGlobalSum = () => {
return globalSumCommon('stats.rx_bytes');
} }
function txGlobalSum() {
return globalSumCommon('stats.tx_bytes')
}
function rxGlobalSum() {
return globalSumCommon('stats.rx_bytes')
}
const peerCount = computed(() => { const peerCount = computed(() => {
if (!peerRouteInfos.value) { if (!peerRouteInfos.value)
return 0; return 0
}
return peerRouteInfos.value.length; return peerRouteInfos.value.length
}); })
// calculate tx/rx rate every 2 seconds // calculate tx/rx rate every 2 seconds
let rateIntervalId = 0; let rateIntervalId = 0
let rateInterval = 2000; const rateInterval = 2000
let prevTxSum = 0; let prevTxSum = 0
let prevRxSum = 0; let prevRxSum = 0
let txRate = ref('0'); const txRate = ref('0')
let rxRate = ref('0'); const rxRate = ref('0')
onMounted(() => { onMounted(() => {
rateIntervalId = window.setInterval(() => { rateIntervalId = window.setInterval(() => {
let curTxSum = txGlobalSum(); const curTxSum = txGlobalSum()
txRate.value = humanFileSize((curTxSum - prevTxSum) / (rateInterval / 1000)); txRate.value = humanFileSize((curTxSum - prevTxSum) / (rateInterval / 1000))
prevTxSum = curTxSum; prevTxSum = curTxSum
let curRxSum = rxGlobalSum(); const curRxSum = rxGlobalSum()
rxRate.value = humanFileSize((curRxSum - prevRxSum) / (rateInterval / 1000)); rxRate.value = humanFileSize((curRxSum - prevRxSum) / (rateInterval / 1000))
prevRxSum = curRxSum; prevRxSum = curRxSum
}, rateInterval); }, rateInterval)
}); })
onUnmounted(() => { onUnmounted(() => {
clearInterval(rateIntervalId); clearInterval(rateIntervalId)
}); })
const dialogVisible = ref(false); const dialogVisible = ref(false)
const dialogContent = ref(''); const dialogContent = ref('')
const showVpnPortalConfig = () => { function showVpnPortalConfig() {
let my_node_info = myNodeInfo.value; const my_node_info = myNodeInfo.value
if (!my_node_info) { if (!my_node_info)
return; return
}
const url = "https://www.wireguardconfig.com/qrcode"; const url = 'https://www.wireguardconfig.com/qrcode'
dialogContent.value = `${my_node_info.vpn_portal_cfg}\n\n # can generate QR code: ${url}`; dialogContent.value = `${my_node_info.vpn_portal_cfg}\n\n # can generate QR code: ${url}`
dialogVisible.value = true; dialogVisible.value = true
} }
const showEventLogs = () => { function showEventLogs() {
let detail = curNetworkInst.value?.detail; const detail = curNetworkInst.value?.detail
if (!detail) { if (!detail)
return; return
}
dialogContent.value = detail.events;
dialogVisible.value = true;
}
dialogContent.value = detail.events
dialogVisible.value = true
}
</script> </script>
<template> <template>
<div> <div>
<Dialog v-model:visible="dialogVisible" modal header="Dialog" :style="{ width: '70%' }"> <Dialog v-model:visible="dialogVisible" modal header="Dialog" :style="{ width: '70%' }">
<Panel> <Panel>
<ScrollPanel style="width: 100%; height: 400px"> <ScrollPanel style="width: 100%; height: 400px">
<pre>{{ dialogContent }}</pre> <pre>{{ dialogContent }}</pre>
</ScrollPanel> </ScrollPanel>
</Panel> </Panel>
<Divider /> <Divider />
<div class="flex justify-content-end gap-2"> <div class="flex justify-content-end gap-2">
<Button type="button" label="Close" @click="dialogVisible = false"></Button> <Button type="button" label="Close" @click="dialogVisible = false" />
</div>
</Dialog>
<Card v-if="curNetworkInst?.error_msg">
<template #title>
Run Network Error
</template>
<template #content>
<div class="flex flex-column gap-y-5">
<div class="text-red-500">
{{ curNetworkInst.error_msg }}
</div>
</div>
</template>
</Card>
<Card v-if="!curNetworkInst?.error_msg">
<template #title>
{{ $t('my_node_info') }}
</template>
<template #content>
<div class="flex w-full flex-column gap-y-5">
<div class="m-0 flex flex-row justify-center gap-x-5">
<div
class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
style="border: 1px solid green"
>
<div class="font-bold">
{{ $t('peer_count') }}
</div>
<div class="text-5xl mt-1">
{{ peerCount }}
</div>
</div> </div>
</Dialog>
<Card v-if="curNetworkInst?.error_msg"> <div
<template #title>Run Network Error</template> class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
<template #content> style="border: 1px solid purple"
<div class="flex flex-column gap-y-5"> >
<div class="text-red-500"> <div class="font-bold">
{{ curNetworkInst.error_msg }} {{ $t('upload') }}
</div> </div>
</div> <div class="text-xl mt-2">
</template> {{ txRate }}/s
</Card> </div>
</div>
<Card v-if="!curNetworkInst?.error_msg"> <div
<template #title>{{ $t('my_node_info') }}</template> class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4"
<template #content> style="border: 1px solid fuchsia"
<div class="flex w-full flex-column gap-y-5"> >
<div class="m-0 flex flex-row justify-center gap-x-5"> <div class="font-bold">
<div class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4" {{ $t('download') }}
style="border: 1px solid green"> </div>
<div class="font-bold"> <div class="text-xl mt-2">
{{ $t('peer_count') }} {{ rxRate }}/s
</div> </div>
<div class="text-5xl mt-1">{{ peerCount }}</div> </div>
</div> </div>
<div class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4" <div class="flex flex-row align-items-center flex-wrap w-full">
style="border: 1px solid purple"> <Chip
<div class="font-bold"> v-for="(chip, i) in myNodeInfoChips" :key="i" :label="chip.label" :icon="chip.icon"
{{ $t('upload') }} class="mr-2 mt-2"
</div> />
<div class="text-xl mt-2">{{ txRate }}/s</div> </div>
</div>
<div class="rounded-full w-36 h-36 flex flex-column align-items-center pt-4" <div v-if="myNodeInfo" class="m-0 flex flex-row justify-center gap-x-5 text-sm">
style="border: 1px solid fuchsia"> <Button severity="info" :label="$t('show_vpn_portal_config')" @click="showVpnPortalConfig" />
<div class="font-bold"> <Button severity="info" :label="$t('show_event_log')" @click="showEventLogs" />
{{ $t('download') }} </div>
</div> </div>
<div class="text-xl mt-2">{{ rxRate }}/s</div> </template>
</div> </Card>
</div>
<div class="flex flex-row align-items-center flex-wrap w-full"> <Divider />
<Chip v-for="chip in myNodeInfoChips" :label="chip.label" :icon="chip.icon" class="mr-2 mt-2">
</Chip>
</div>
<div class="m-0 flex flex-row justify-center gap-x-5 text-sm" v-if="myNodeInfo"> <Card v-if="!curNetworkInst?.error_msg">
<Button severity="info" :label="$t('show_vpn_portal_config')" @click="showVpnPortalConfig" /> <template #title>
<Button severity="info" :label="$t('show_event_log')" @click="showEventLogs" /> {{ $t('peer_info') }}
</div> </template>
</div> <template #content>
</template> <DataTable :value="peerRouteInfos" table-style="min-width: 50rem">
</Card> <Column field="route.ipv4_addr" :header="$t('virtual_ipv4')" />
<Column field="route.hostname" :header="$t('hostname')" />
<Divider /> <Column :field="routeCost" :header="$t('route_cost')" />
<Column :field="latencyMs" :header="$t('latency')" />
<Card v-if="!curNetworkInst?.error_msg"> <Column :field="txBytes" :header="$t('upload_bytes')" />
<template #title>{{ $t('peer_info') }}</template> <Column :field="rxBytes" :header="$t('download_bytes')" />
<template #content> <Column :field="lossRate" :header="$t('loss_rate')" />
<DataTable :value="peerRouteInfos" tableStyle="min-width: 50rem"> </DataTable>
<Column field="route.ipv4_addr" :header="$t('virtual_ipv4')"></Column> </template>
<Column field="route.hostname" :header="$t('hostname')"></Column> </Card>
<Column :field="routeCost" :header="$t('route_cost')"></Column> </div>
<Column :field="latencyMs" :header="$t('latency')"></Column> </template>
<Column :field="txBytes" :header="$t('upload_bytes')"></Column>
<Column :field="rxBytes" :header="$t('download_bytes')"></Column>
<Column :field="lossRate" :header="$t('loss_rate')"></Column>
</DataTable>
</template>
</Card>
</div>
</template>

View File

@ -1,22 +1,22 @@
import { NetworkConfig } from "~/types/network"; import { invoke } from '@tauri-apps/api/tauri'
import { invoke } from "@tauri-apps/api/tauri"; import type { NetworkConfig } from '~/types/network'
export async function parseNetworkConfig(cfg: NetworkConfig): Promise<string> { export async function parseNetworkConfig(cfg: NetworkConfig): Promise<string> {
const ret: string = await invoke("parse_network_config", { cfg: JSON.stringify(cfg) }); const ret: string = await invoke('parse_network_config', { cfg: JSON.stringify(cfg) })
return ret; return ret
} }
export async function runNetworkInstance(cfg: NetworkConfig) { export async function runNetworkInstance(cfg: NetworkConfig) {
const ret: string = await invoke("run_network_instance", { cfg: JSON.stringify(cfg) }); const ret: string = await invoke('run_network_instance', { cfg: JSON.stringify(cfg) })
return ret; return ret
} }
export async function retainNetworkInstance(instanceIds: Array<string>) { export async function retainNetworkInstance(instanceIds: Array<string>) {
const ret: string = await invoke("retain_network_instance", { instanceIds: JSON.stringify(instanceIds) }); const ret: string = await invoke('retain_network_instance', { instanceIds: JSON.stringify(instanceIds) })
return ret; return ret
} }
export async function collectNetworkInfos() { export async function collectNetworkInfos() {
const ret: string = await invoke("collect_network_infos", {}); const ret: string = await invoke('collect_network_infos', {})
return JSON.parse(ret); return JSON.parse(ret)
} }

View File

@ -1,3 +1,3 @@
<template> <template>
<RouterView /> <RouterView />
</template> </template>

View File

@ -1,37 +1,37 @@
import { setupLayouts } from "virtual:generated-layouts"; import { setupLayouts } from 'virtual:generated-layouts'
import { createRouter, createWebHistory } from "vue-router/auto"; import { createRouter, createWebHistory } from 'vue-router/auto'
import PrimeVue from 'primevue/config'; import PrimeVue from 'primevue/config'
import ToastService from 'primevue/toastservice'; import ToastService from 'primevue/toastservice'
import App from "~/App.vue"; import App from '~/App.vue'
import "~/styles.css"; import '~/styles.css'
import "primevue/resources/themes/aura-light-green/theme.css"; import 'primevue/resources/themes/aura-light-green/theme.css'
import "primeicons/primeicons.css"; import 'primeicons/primeicons.css'
import "primeflex/primeflex.css"; import 'primeflex/primeflex.css'
import { i18n, loadLanguageAsync } from "~/modules/i18n"; import { i18n, loadLanguageAsync } from '~/modules/i18n'
if (import.meta.env.PROD) { if (import.meta.env.PROD) {
document.addEventListener("keydown", (event) => { document.addEventListener('keydown', (event) => {
if ( if (
event.key === "F5" || event.key === 'F5'
(event.ctrlKey && event.key === "r") || || (event.ctrlKey && event.key === 'r')
(event.metaKey && event.key === "r") || (event.metaKey && event.key === 'r')
) )
event.preventDefault(); event.preventDefault()
}); })
document.addEventListener("contextmenu", (event) => { document.addEventListener('contextmenu', (event) => {
event.preventDefault(); event.preventDefault()
}); })
} }
const app = createApp(App) const app = createApp(App)
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),
extendRoutes: (routes) => setupLayouts(routes), extendRoutes: routes => setupLayouts(routes),
}); })
app.use(router) app.use(router)
app.use(createPinia()) app.use(createPinia())
@ -40,5 +40,4 @@ loadLanguageAsync(localStorage.getItem('lang') || 'en')
app.use(PrimeVue) app.use(PrimeVue)
app.use(ToastService) app.use(ToastService)
app.mount("#app") app.mount('#app')

View File

@ -21,7 +21,7 @@ const loadedLanguages: string[] = []
function setI18nLanguage(lang: Locale) { function setI18nLanguage(lang: Locale) {
i18n.global.locale.value = lang as any i18n.global.locale.value = lang as any
localStorage.setItem("locale", lang); localStorage.setItem('locale', lang)
return lang return lang
} }
@ -35,15 +35,16 @@ export async function loadLanguageAsync(lang: string): Promise<Locale> {
return setI18nLanguage(lang) return setI18nLanguage(lang)
// If the language hasn't been loaded yet // If the language hasn't been loaded yet
let messages; let messages
try { try {
messages = await localesMap[lang]() messages = await localesMap[lang]()
} catch { }
messages = await localesMap['en']() catch {
messages = await localesMap.en()
} }
i18n.global.setLocaleMessage(lang, messages.default) i18n.global.setLocaleMessage(lang, messages.default)
loadedLanguages.push(lang) loadedLanguages.push(lang)
return setI18nLanguage(lang) return setI18nLanguage(lang)
} }

View File

@ -1,19 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import Stepper from 'primevue/stepper'; import Stepper from 'primevue/stepper'
import StepperPanel from 'primevue/stepperpanel'; import StepperPanel from 'primevue/stepperpanel'
import { useToast } from "primevue/usetoast"; import { useToast } from 'primevue/usetoast'
import { exit } from '@tauri-apps/api/process'
import Config from '~/components/Config.vue'
import Status from '~/components/Status.vue'
import Config from '~/components/Config.vue'; import type { NetworkConfig } from '~/types/network'
import Status from '~/components/Status.vue'; import { loadLanguageAsync } from '~/modules/i18n'
import { exit } from '@tauri-apps/api/process';
import { NetworkConfig } from '~/types/network';
import { loadLanguageAsync } from '~/modules/i18n';
const { t, locale } = useI18n() const { t, locale } = useI18n()
const visible = ref(false); const visible = ref(false)
const tomlConfig = ref(""); const tomlConfig = ref('')
const items = ref([ const items = ref([
{ {
@ -21,103 +21,106 @@ const items = ref([
icon: 'pi pi-file-edit', icon: 'pi pi-file-edit',
command: async () => { command: async () => {
try { try {
const ret = await parseNetworkConfig(networkStore.curNetwork); const ret = await parseNetworkConfig(networkStore.curNetwork)
tomlConfig.value = ret; tomlConfig.value = ret
} catch (e: any) {
tomlConfig.value = e;
} }
visible.value = true; catch (e: any) {
} tomlConfig.value = e
}
visible.value = true
},
}, },
{ {
label: () => t('del_cur_network'), label: () => t('del_cur_network'),
icon: 'pi pi-times', icon: 'pi pi-times',
command: async () => { command: async () => {
networkStore.removeNetworkInstance(networkStore.curNetwork.instance_id); networkStore.removeNetworkInstance(networkStore.curNetwork.instance_id)
await retainNetworkInstance(networkStore.networkInstanceIds); await retainNetworkInstance(networkStore.networkInstanceIds)
networkStore.delCurNetwork(); networkStore.delCurNetwork()
}, },
disabled: () => networkStore.networkList.length <= 1, disabled: () => networkStore.networkList.length <= 1,
}, },
]) ])
enum Severity { enum Severity {
None = "none", None = 'none',
Success = "success", Success = 'success',
Info = "info", Info = 'info',
Warn = "warn", Warn = 'warn',
Error = "error", Error = 'error',
} }
const messageBarSeverity = ref(Severity.None); const messageBarSeverity = ref(Severity.None)
const messageBarContent = ref(""); const messageBarContent = ref('')
const toast = useToast(); const toast = useToast()
const networkStore = useNetworkStore(); const networkStore = useNetworkStore()
const addNewNetwork = () => { function addNewNetwork() {
networkStore.addNewNetwork(); networkStore.addNewNetwork()
networkStore.curNetwork = networkStore.lastNetwork; networkStore.curNetwork = networkStore.lastNetwork
} }
const networkMenuName = (network: NetworkConfig) => { function networkMenuName(network: NetworkConfig) {
return network.network_name + " (" + network.instance_id + ")"; return `${network.network_name} (${network.instance_id})`
} }
networkStore.$subscribe(async () => { networkStore.$subscribe(async () => {
networkStore.saveToLocalStorage(); networkStore.saveToLocalStorage()
try { try {
await parseNetworkConfig(networkStore.curNetwork); await parseNetworkConfig(networkStore.curNetwork)
messageBarSeverity.value = Severity.None; messageBarSeverity.value = Severity.None
} catch (e: any) {
messageBarContent.value = e;
messageBarSeverity.value = Severity.Error;
} }
}); catch (e: any) {
messageBarContent.value = e
messageBarSeverity.value = Severity.Error
}
})
async function runNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) { async function runNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) {
cb({} as MouseEvent); cb({} as MouseEvent)
networkStore.removeNetworkInstance(cfg.instance_id); networkStore.removeNetworkInstance(cfg.instance_id)
await retainNetworkInstance(networkStore.networkInstanceIds); await retainNetworkInstance(networkStore.networkInstanceIds)
networkStore.addNetworkInstance(cfg.instance_id); networkStore.addNetworkInstance(cfg.instance_id)
try { try {
await runNetworkInstance(cfg); await runNetworkInstance(cfg)
} catch (e: any) { }
console.error(e); catch (e: any) {
toast.add({ severity: 'info', detail: e }); // console.error(e)
toast.add({ severity: 'info', detail: e })
} }
} }
async function stopNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) { async function stopNetworkCb(cfg: NetworkConfig, cb: (e: MouseEvent) => void) {
console.log("stopNetworkCb", cfg, cb); // console.log('stopNetworkCb', cfg, cb)
cb({} as MouseEvent); cb({} as MouseEvent)
networkStore.removeNetworkInstance(cfg.instance_id); networkStore.removeNetworkInstance(cfg.instance_id)
await retainNetworkInstance(networkStore.networkInstanceIds); await retainNetworkInstance(networkStore.networkInstanceIds)
} }
async function updateNetworkInfos() { async function updateNetworkInfos() {
networkStore.updateWithNetworkInfos(await collectNetworkInfos()); networkStore.updateWithNetworkInfos(await collectNetworkInfos())
} }
let intervalId = 0; let intervalId = 0
onMounted(() => { onMounted(() => {
intervalId = window.setInterval(async () => { intervalId = window.setInterval(async () => {
await updateNetworkInfos(); await updateNetworkInfos()
}, 500); }, 500)
}); })
onUnmounted(() => clearInterval(intervalId)) onUnmounted(() => clearInterval(intervalId))
const curNetworkHasInstance = computed(() => { const curNetworkHasInstance = computed(() => {
return networkStore.networkInstanceIds.includes(networkStore.curNetworkId); return networkStore.networkInstanceIds.includes(networkStore.curNetworkId)
}); })
const activeStep = computed(() => { const activeStep = computed(() => {
return curNetworkHasInstance.value ? 1 : 0; return curNetworkHasInstance.value ? 1 : 0
}); })
const setting_menu = ref(); const setting_menu = ref()
const setting_menu_items = ref([ const setting_menu_items = ref([
{ {
label: () => t('settings'), label: () => t('settings'),
@ -126,28 +129,30 @@ const setting_menu_items = ref([
label: () => t('exchange_language'), label: () => t('exchange_language'),
icon: 'pi pi-language', icon: 'pi pi-language',
command: async () => { command: async () => {
await loadLanguageAsync((locale.value === 'en' ? 'cn' : 'en')); await loadLanguageAsync((locale.value === 'en' ? 'cn' : 'en'))
} },
}, },
{ {
label: () => t('exit'), label: () => t('exit'),
icon: 'pi pi-times', icon: 'pi pi-times',
command: async () => { command: async () => {
await exit(1); await exit(1)
} },
} },
] ],
} },
]); ])
const toggle_setting_menu = (event: any) => { function toggle_setting_menu(event: any) {
setting_menu.value.toggle(event); setting_menu.value.toggle(event)
}; }
onMounted(async () => { onMounted(async () => {
networkStore.loadFromLocalStorage(); networkStore.loadFromLocalStorage()
}); })
</script>
<script lang="ts">
</script> </script>
<template> <template>
@ -161,7 +166,7 @@ onMounted(async () => {
</Panel> </Panel>
<Divider /> <Divider />
<div class="flex justify-content-end gap-2"> <div class="flex justify-content-end gap-2">
<Button type="button" :label="$t('close')" @click="visible = false"></Button> <Button type="button" :label="$t('close')" @click="visible = false" />
</div> </div>
</Dialog> </Dialog>
@ -169,23 +174,29 @@ onMounted(async () => {
<Toolbar> <Toolbar>
<template #start> <template #start>
<div class="flex align-items-center gap-2"> <div class="flex align-items-center gap-2">
<Button icon="pi pi-plus" class="mr-2" severity="primary" :label="$t('add_new_network')" <Button
@click="addNewNetwork" /> icon="pi pi-plus" class="mr-2" severity="primary" :label="$t('add_new_network')"
@click="addNewNetwork"
/>
</div> </div>
</template> </template>
<template #center> <template #center>
<div class="min-w-80 mr-20"> <div class="min-w-80 mr-20">
<Dropdown v-model="networkStore.curNetwork" :options="networkStore.networkList" <Dropdown
:optionLabel="networkMenuName" :placeholder="$t('select_network')" :highlightOnSelect="true" v-model="networkStore.curNetwork" :options="networkStore.networkList"
:checkmark="true" class="w-full md:w-32rem" /> :option-label="networkMenuName" :placeholder="$t('select_network')" :highlight-on-select="true"
:checkmark="true" class="w-full md:w-32rem"
/>
</div> </div>
</template> </template>
<template #end> <template #end>
<Button icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" @click="toggle_setting_menu" <Button
:label="$t('settings')" aria-controls="overlay_setting_menu" /> icon="pi pi-cog" class="mr-2" severity="secondary" aria-haspopup="true" :label="$t('settings')"
<Menu ref="setting_menu" id="overlay_setting_menu" :model="setting_menu_items" :popup="true" /> aria-controls="overlay_setting_menu" @click="toggle_setting_menu"
/>
<Menu id="overlay_setting_menu" ref="setting_menu" :model="setting_menu_items" :popup="true" />
</template> </template>
</Toolbar> </Toolbar>
</div> </div>
@ -193,8 +204,10 @@ onMounted(async () => {
<Stepper class="h-full overflow-y-auto" :active-step="activeStep"> <Stepper class="h-full overflow-y-auto" :active-step="activeStep">
<StepperPanel :header="$t('config_network')" class="w"> <StepperPanel :header="$t('config_network')" class="w">
<template #content="{ nextCallback }"> <template #content="{ nextCallback }">
<Config @run-network="runNetworkCb($event, nextCallback)" :instance-id="networkStore.curNetworkId" <Config
:config-invalid="messageBarSeverity != Severity.None" /> :instance-id="networkStore.curNetworkId" :config-invalid="messageBarSeverity !== Severity.None"
@run-network="runNetworkCb($event, nextCallback)"
/>
</template> </template>
</StepperPanel> </StepperPanel>
<StepperPanel :header="$t('running')"> <StepperPanel :header="$t('running')">
@ -203,21 +216,22 @@ onMounted(async () => {
<Status :instance-id="networkStore.curNetworkId" /> <Status :instance-id="networkStore.curNetworkId" />
</div> </div>
<div class="flex pt-4 justify-content-center"> <div class="flex pt-4 justify-content-center">
<Button :label="$t('stop_network')" severity="danger" icon="pi pi-arrow-left" <Button
@click="stopNetworkCb(networkStore.curNetwork, prevCallback)" /> :label="$t('stop_network')" severity="danger" icon="pi pi-arrow-left"
@click="stopNetworkCb(networkStore.curNetwork, prevCallback)"
/>
</div> </div>
</template> </template>
</StepperPanel> </StepperPanel>
</Stepper> </Stepper>
<div> <div>
<Menubar :model="items" breakpoint="300px"> <Menubar :model="items" breakpoint="300px" />
</Menubar>
<InlineMessage v-if="messageBarSeverity !== Severity.None" class="absolute bottom-0 right-0" severity="error"> <InlineMessage v-if="messageBarSeverity !== Severity.None" class="absolute bottom-0 right-0" severity="error">
{{ messageBarContent }}</InlineMessage> {{ messageBarContent }}
</InlineMessage>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
@ -240,13 +254,9 @@ body {
margin: 0; margin: 0;
} }
/* /*
.p-tabview-panel { .p-tabview-panel {
height: 100%; height: 100%;
} */ } */
</style> </style>
<script lang="ts">
</script>

View File

@ -1,8 +1,9 @@
import { DEFAULT_NETWORK_CONFIG, NetworkConfig, NetworkInstance } from '~/types/network'; import type { NetworkConfig, NetworkInstance } from '~/types/network'
import { DEFAULT_NETWORK_CONFIG } from '~/types/network'
export const useNetworkStore = defineStore('networkStore', { export const useNetworkStore = defineStore('networkStore', {
state: () => { state: () => {
const networkList = [DEFAULT_NETWORK_CONFIG()]; const networkList = [DEFAULT_NETWORK_CONFIG()]
return { return {
// for initially empty lists // for initially empty lists
networkList: networkList as NetworkConfig[], networkList: networkList as NetworkConfig[],
@ -18,80 +19,80 @@ export const useNetworkStore = defineStore('networkStore', {
getters: { getters: {
lastNetwork(): NetworkConfig { lastNetwork(): NetworkConfig {
return this.networkList[this.networkList.length - 1]; return this.networkList[this.networkList.length - 1]
}, },
curNetworkId(): string { curNetworkId(): string {
return this.curNetwork.instance_id; return this.curNetwork.instance_id
}, },
networkInstances(): Array<NetworkInstance> { networkInstances(): Array<NetworkInstance> {
return Object.values(this.instances); return Object.values(this.instances)
}, },
networkInstanceIds(): Array<string> { networkInstanceIds(): Array<string> {
return Object.keys(this.instances); return Object.keys(this.instances)
} },
}, },
actions: { actions: {
addNewNetwork() { addNewNetwork() {
this.networkList.push(DEFAULT_NETWORK_CONFIG()); this.networkList.push(DEFAULT_NETWORK_CONFIG())
}, },
delCurNetwork() { delCurNetwork() {
const curNetworkIdx = this.networkList.indexOf(this.curNetwork); const curNetworkIdx = this.networkList.indexOf(this.curNetwork)
this.networkList.splice(curNetworkIdx, 1); this.networkList.splice(curNetworkIdx, 1)
const nextCurNetworkIdx = Math.min(curNetworkIdx, this.networkList.length - 1); const nextCurNetworkIdx = Math.min(curNetworkIdx, this.networkList.length - 1)
this.curNetwork = this.networkList[nextCurNetworkIdx]; this.curNetwork = this.networkList[nextCurNetworkIdx]
}, },
removeNetworkInstance(instanceId: string) { removeNetworkInstance(instanceId: string) {
delete this.instances[instanceId]; delete this.instances[instanceId]
}, },
addNetworkInstance(instanceId: string) { addNetworkInstance(instanceId: string) {
this.instances[instanceId] = { this.instances[instanceId] = {
instance_id: instanceId, instance_id: instanceId,
running: false, running: false,
error_msg: "", error_msg: '',
detail: {}, detail: {},
}; }
}, },
updateWithNetworkInfos(networkInfos: Record<string, any>) { updateWithNetworkInfos(networkInfos: Record<string, any>) {
this.networkInfos = networkInfos; this.networkInfos = networkInfos
for (const [instanceId, info] of Object.entries(networkInfos)) { for (const [instanceId, info] of Object.entries(networkInfos)) {
if (this.instances[instanceId] === undefined) { if (this.instances[instanceId] === undefined)
this.addNetworkInstance(instanceId); this.addNetworkInstance(instanceId)
}
this.instances[instanceId].running = info["running"]; this.instances[instanceId].running = info.running
this.instances[instanceId].error_msg = info["error_msg"]; this.instances[instanceId].error_msg = info.error_msg
this.instances[instanceId].detail = info; this.instances[instanceId].detail = info
} }
}, },
loadFromLocalStorage() { loadFromLocalStorage() {
const networkList = JSON.parse(localStorage.getItem("networkList") || '[]'); const networkList = JSON.parse(localStorage.getItem('networkList') || '[]')
let result = []; const result = []
for (const cfg of networkList) { for (const cfg of networkList) {
result.push({ result.push({
...DEFAULT_NETWORK_CONFIG, ...DEFAULT_NETWORK_CONFIG,
...cfg, ...cfg,
}); })
} }
if (result.length === 0) { if (result.length === 0)
result.push(DEFAULT_NETWORK_CONFIG); result.push(DEFAULT_NETWORK_CONFIG)
}
this.networkList = result; this.networkList = result
this.curNetwork = this.networkList[0]; this.curNetwork = this.networkList[0]
}, },
saveToLocalStorage() { saveToLocalStorage() {
localStorage.setItem("networkList", JSON.stringify(this.networkList)); localStorage.setItem('networkList', JSON.stringify(this.networkList))
} },
} },
}) })
if (import.meta.hot) if (import.meta.hot)
import.meta.hot.accept(acceptHMRUpdate(useNetworkStore as any, import.meta.hot)) import.meta.hot.accept(acceptHMRUpdate(useNetworkStore as any, import.meta.hot))

View File

@ -1,12 +1,12 @@
@layer tailwind-base, primevue, tailwind-utilities; @layer tailwind-base, primevue, tailwind-utilities;
@layer tailwind-base { @layer tailwind-base {
@tailwind base; @tailwind base;
} }
@layer tailwind-utilities { @layer tailwind-utilities {
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
} }
:root { :root {
@ -26,8 +26,8 @@
} }
.card { .card {
background: var(--surface-card); background: var(--surface-card);
padding: 2rem; padding: 2rem;
border-radius: 10px; border-radius: 10px;
margin-bottom: 1rem; margin-bottom: 1rem;
} }

View File

@ -1,72 +1,72 @@
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid'
export enum NetworkingMethod { export enum NetworkingMethod {
PublicServer = "PublicServer", PublicServer = 'PublicServer',
Manual = "Manual", Manual = 'Manual',
Standalone = "Standalone", Standalone = 'Standalone',
} }
export interface NetworkConfig { export interface NetworkConfig {
instance_id: string, instance_id: string
virtual_ipv4: string virtual_ipv4: string
network_name: string network_name: string
network_secret: string network_secret: string
networking_method: NetworkingMethod, networking_method: NetworkingMethod
public_server_url: string, public_server_url: string
peer_urls: Array<string>, peer_urls: Array<string>
proxy_cidrs: Array<string>, proxy_cidrs: Array<string>
enable_vpn_portal: boolean, enable_vpn_portal: boolean
vpn_portal_listne_port: number, vpn_portal_listne_port: number
vpn_portal_client_network_addr: string, vpn_portal_client_network_addr: string
vpn_portal_client_network_len: number, vpn_portal_client_network_len: number
advanced_settings: boolean, advanced_settings: boolean
listener_urls: Array<string>, listener_urls: Array<string>
rpc_port: number, rpc_port: number
} }
export const DEFAULT_NETWORK_CONFIG = (): NetworkConfig => { export function DEFAULT_NETWORK_CONFIG(): NetworkConfig {
return { return {
instance_id: uuidv4(), instance_id: uuidv4(),
virtual_ipv4: "", virtual_ipv4: '',
network_name: "default", network_name: 'default',
network_secret: "", network_secret: '',
networking_method: NetworkingMethod.PublicServer, networking_method: NetworkingMethod.PublicServer,
public_server_url: "tcp://easytier.public.kkrainbow.top:11010", public_server_url: 'tcp://easytier.public.kkrainbow.top:11010',
peer_urls: [], peer_urls: [],
proxy_cidrs: [], proxy_cidrs: [],
enable_vpn_portal: false, enable_vpn_portal: false,
vpn_portal_listne_port: 22022, vpn_portal_listne_port: 22022,
vpn_portal_client_network_addr: "", vpn_portal_client_network_addr: '',
vpn_portal_client_network_len: 24, vpn_portal_client_network_len: 24,
advanced_settings: false, advanced_settings: false,
listener_urls: [ listener_urls: [
"tcp://0.0.0.0:11010", 'tcp://0.0.0.0:11010',
"udp://0.0.0.0:11010", 'udp://0.0.0.0:11010',
"wg://0.0.0.0:11011", 'wg://0.0.0.0:11011',
], ],
rpc_port: 15888, rpc_port: 15888,
} }
} }
export interface NetworkInstance { export interface NetworkInstance {
instance_id: string, instance_id: string
running: boolean, running: boolean
error_msg: string, error_msg: string
detail: any, detail: any
} }

View File

@ -1,12 +1,11 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: [
"./index.html", './index.html',
"./src/**/*.{vue,js,ts,jsx,tsx}", './src/**/*.{vue,js,ts,jsx,tsx}',
], ],
theme: { theme: {
extend: {}, extend: {},
}, },
plugins: [], plugins: [],
} }

View File

@ -9,8 +9,6 @@
"baseUrl": ".", "baseUrl": ".",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"paths": { "paths": {
"~/*": [ "~/*": [
"src/*" "src/*"
@ -23,10 +21,12 @@
"unplugin-vue-macros/macros-global", "unplugin-vue-macros/macros-global",
"unplugin-vue-router/client" "unplugin-vue-router/client"
], ],
"allowImportingTsExtensions": true,
"allowJs": true, "allowJs": true,
"strict": true, "strict": true,
"strictNullChecks": true, "strictNullChecks": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noEmit": true,
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"isolatedModules": true, "isolatedModules": true,
@ -42,4 +42,4 @@
"dist", "dist",
"node_modules" "node_modules"
] ]
} }

View File

@ -9,13 +9,13 @@ import VueI18n from '@intlify/unplugin-vue-i18n/vite'
import VueDevTools from 'vite-plugin-vue-devtools' import VueDevTools from 'vite-plugin-vue-devtools'
import VueRouter from 'unplugin-vue-router/vite' import VueRouter from 'unplugin-vue-router/vite'
import { VueRouterAutoImports } from 'unplugin-vue-router' import { VueRouterAutoImports } from 'unplugin-vue-router'
import { PrimeVueResolver } from 'unplugin-vue-components/resolvers'; import { PrimeVueResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig(async () => ({ export default defineConfig(async () => ({
resolve: { resolve: {
alias: { alias: {
"~/": `${path.resolve(__dirname, "src")}/`, '~/': `${path.resolve(__dirname, 'src')}/`,
}, },
}, },
plugins: [ plugins: [
@ -64,8 +64,8 @@ export default defineConfig(async () => ({
include: [/\.vue$/, /\.vue\?vue/, /\.md$/], include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
dts: 'src/components.d.ts', dts: 'src/components.d.ts',
resolvers: [ resolvers: [
PrimeVueResolver() PrimeVueResolver(),
] ],
}), }),
// https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n // https://github.com/intlify/bundle-tools/tree/main/packages/unplugin-vue-i18n
@ -90,7 +90,7 @@ export default defineConfig(async () => ({
strictPort: true, strictPort: true,
watch: { watch: {
// 3. tell vite to ignore watching `src-tauri` // 3. tell vite to ignore watching `src-tauri`
ignored: ["**/src-tauri/**"], ignored: ['**/src-tauri/**'],
}, },
}, },
})); }))