This commit is contained in:
snltty
2024-09-04 17:41:53 +08:00
parent 453ca1f6f2
commit 3824e514bb
21 changed files with 283 additions and 225 deletions

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;

View File

@@ -7,7 +7,6 @@ using System.Collections.Concurrent;
using System.Net.Sockets; using System.Net.Sockets;
using System.Net; using System.Net;
using linker.tunnel.wanport; using linker.tunnel.wanport;
using System.Collections.Generic;
namespace linker.tunnel namespace linker.tunnel
{ {
@@ -186,9 +185,12 @@ namespace linker.tunnel
LoggerHelper.Instance.Error($"tunnel {transport.Name} get remote {remoteMachineId} external ip fail "); LoggerHelper.Instance.Error($"tunnel {transport.Name} get remote {remoteMachineId} external ip fail ");
break; break;
} }
LoggerHelper.Instance.Info($"tunnel {transport.Name} got local external ip {localInfo.Result.ToJson()}"); if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transport.Name} got remote external ip {remoteInfo.Result.ToJson()}"); LoggerHelper.Instance.Info($"tunnel {transport.Name} got local external ip {localInfo.Result.ToJson()}");
LoggerHelper.Instance.Info($"tunnel {transportItem.ToJson()}"); if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transport.Name} got remote external ip {remoteInfo.Result.ToJson()}");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel {transportItem.ToJson()}");
tunnelTransportInfo = new TunnelTransportInfo tunnelTransportInfo = new TunnelTransportInfo
@@ -332,13 +334,13 @@ namespace linker.tunnel
private void OnConnecting(TunnelTransportInfo tunnelTransportInfo) private void OnConnecting(TunnelTransportInfo tunnelTransportInfo)
{ {
//if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel connecting {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}"); LoggerHelper.Instance.Info($"tunnel connecting {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}");
} }
private void OnConnectBegin(TunnelTransportInfo tunnelTransportInfo) private void OnConnectBegin(TunnelTransportInfo tunnelTransportInfo)
{ {
//if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Info($"tunnel connecting from {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}"); LoggerHelper.Instance.Info($"tunnel connecting from {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName}");
} }
/// <summary> /// <summary>
@@ -347,8 +349,8 @@ namespace linker.tunnel
/// <param name="connection"></param> /// <param name="connection"></param>
private void _OnConnected(ITunnelConnection connection) private void _OnConnected(ITunnelConnection connection)
{ {
//if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Debug($"tunnel connect {connection.RemoteMachineId}->{connection.RemoteMachineName} success->{connection.IPEndPoint}"); LoggerHelper.Instance.Debug($"tunnel connect {connection.RemoteMachineId}->{connection.RemoteMachineName} success->{connection.IPEndPoint}");
//调用以下别人注册的回调 //调用以下别人注册的回调
if (OnConnected.TryGetValue(Helper.GlobalString, out List<Action<ITunnelConnection>> callbacks)) if (OnConnected.TryGetValue(Helper.GlobalString, out List<Action<ITunnelConnection>> callbacks))
@@ -368,7 +370,8 @@ namespace linker.tunnel
} }
private void OnConnectFail(TunnelTransportInfo tunnelTransportInfo) private void OnConnectFail(TunnelTransportInfo tunnelTransportInfo)
{ {
LoggerHelper.Instance.Error($"tunnel connect {tunnelTransportInfo.Remote.MachineId} fail"); if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"tunnel connect {tunnelTransportInfo.Remote.MachineId} fail");
} }
private void ParseRemoteEndPoint(TunnelTransportInfo tunnelTransportInfo) private void ParseRemoteEndPoint(TunnelTransportInfo tunnelTransportInfo)
@@ -425,7 +428,8 @@ namespace linker.tunnel
{ {
if (AddBackground(remoteMachineId, transactionId) == false) if (AddBackground(remoteMachineId, transactionId) == false)
{ {
LoggerHelper.Instance.Error($"tunnel background {remoteMachineId}@{transactionId} already exists"); if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Error($"tunnel background {remoteMachineId}@{transactionId} already exists");
return; return;
} }
Task.Run(async () => Task.Run(async () =>

View File

@@ -308,7 +308,9 @@ namespace linker.tunnel.transport
await targetSocket.SendToAsync($"{flagTexts}-{tunnelTransportInfo.Local.MachineId}-{tunnelTransportInfo.FlowId}".ToBytes(), ep).ConfigureAwait(false); await targetSocket.SendToAsync($"{flagTexts}-{tunnelTransportInfo.Local.MachineId}-{tunnelTransportInfo.FlowId}".ToBytes(), ep).ConfigureAwait(false);
await targetSocket.ReceiveFromAsync(new byte[1024], new IPEndPoint(IPAddress.Any, 0)).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false); await targetSocket.ReceiveFromAsync(new byte[1024], new IPEndPoint(IPAddress.Any, 0)).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false);
LoggerHelper.Instance.Debug($"{Name} connect to {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName} {ep} success");
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
LoggerHelper.Instance.Debug($"{Name} connect to {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName} {ep} success");
TunnelConnectionUdp result = new TunnelConnectionUdp TunnelConnectionUdp result = new TunnelConnectionUdp
{ {

View File

@@ -6,4 +6,7 @@ export const getConfig = () => {
export const install = (data) => { export const install = (data) => {
return sendWebsocketMsg('configclient/install', data); return sendWebsocketMsg('configclient/install', data);
}
export const exportConfig = () => {
return sendWebsocketMsg('configclient/export');
} }

View File

@@ -1,15 +1,13 @@
<template> <template>
<div v-if="config" class="status-api-wrap" :class="{connected:connected}"> <div v-if="config" class="status-api-wrap" :class="{connected:connected}">
<template > <el-popconfirm confirm-button-text="清除" cancel-button-text="更改" title="确定你的操作?" @cancel="handleShow" @confirm="handleResetConnect" >
<el-popconfirm confirm-button-text="清除" cancel-button-text="更改" title="确定你的操作?" @cancel="handleShow" @confirm="handleResetConnect" > <template #reference>
<template #reference> <a href="javascript:;" title="此设备的管理接口">
<a href="javascript:;" title="此设备的管理接口"> <el-icon size="16"><Tools /></el-icon>
<el-icon size="16"><Tools /></el-icon> 管理接口
管理接口 </a>
</a> </template>
</template> </el-popconfirm>
</el-popconfirm>
</template>
<el-dialog class="options-center" title="管理接口" destroy-on-close v-model="showPort" center :show-close="false" <el-dialog class="options-center" title="管理接口" destroy-on-close v-model="showPort" center :show-close="false"
:close-on-click-modal="false" align-center width="200"> :close-on-click-modal="false" align-center width="200">
<div class="port-wrap t-c"> <div class="port-wrap t-c">

View File

@@ -33,6 +33,7 @@
</div> </div>
<div class="flex-1"></div> <div class="flex-1"></div>
<div class="share"><Share :config="config"></Share></div>
<div class="api"><Api :config="config"></Api></div> <div class="api"><Api :config="config"></Api></div>
<div class="server" ><Server :config="config"></Server></div> <div class="server" ><Server :config="config"></Server></div>
@@ -48,12 +49,13 @@
import { computed, h, reactive, ref } from 'vue'; import { computed, h, reactive, ref } from 'vue';
import Api from './Api.vue' import Api from './Api.vue'
import Server from './Server.vue' import Server from './Server.vue'
import Share from './Share.vue'
import { injectGlobalData } from '@/provide'; import { injectGlobalData } from '@/provide';
import {Download,Loading,CircleCheck} from '@element-plus/icons-vue' import {Download,Loading,CircleCheck} from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox,ElSelect,ElOption } from 'element-plus'; import { ElMessage, ElMessageBox,ElSelect,ElOption } from 'element-plus';
import { confirm, exit } from '@/apis/updater'; import { confirm, exit } from '@/apis/updater';
export default { export default {
components:{Api,Server,Download,Loading,CircleCheck}, components:{Api,Server,Share,Download,Loading,CircleCheck},
props:['config'], props:['config'],
setup(props) { setup(props) {
@@ -92,6 +94,7 @@ export default {
? 'yellow' :'green' ? 'yellow' :'green'
}); });
const handleUpdate = ()=>{ const handleUpdate = ()=>{
if(!props.config) return;
if(!updater.value.Version){ if(!updater.value.Version){
ElMessage.error('未检测到更新'); ElMessage.error('未检测到更新');
return; return;

View File

@@ -0,0 +1,63 @@
<template>
<div v-if="config" class="status-share-wrap">
<a href="javascript:;" title="此设备的管理接口" @click="state.show = true">
<el-icon size="16"><Share /></el-icon>
导出配置
</a>
<el-dialog class="options-center" title="导出配置" destroy-on-close v-model="state.show" center width="300" top="1vh">
<div class="port-wrap t-c">
导出配置作为节点客户端运行其仅有查看基本信息的能力无法修改任何配置如果使用docker可以仅复制configs文件夹过去docker映射配置文件夹即可
</div>
<template #footer>
<el-button plain @click="state.show = false" :loading="state.loading">取消</el-button>
<el-button type="success" plain @click="handleExport" :loading="state.loading">确定导出</el-button>
</template>
</el-dialog>
</div>
</template>
<script>
import { reactive } from 'vue';
import {Share} from '@element-plus/icons-vue'
import { exportConfig } from '@/apis/config';
import { ElMessage } from 'element-plus';
export default {
components:{Share},
props:['config'],
setup(props) {
const state = reactive({
show: false,
loading:false
});
const handleExport = ()=>{
state.loading = true;
exportConfig().then(()=>{
state.loading = false;
state.show = false;
ElMessage.success('导出成功');
const link = document.createElement('a');
link.download = 'client-node-export.zip';
link.href = '/client-node-export.zip';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}).catch(()=>{
state.loading = false;
});
}
return {config:props.config, state,handleExport};
}
}
</script>
<style lang="stylus" scoped>
.status-share-wrap{
padding-right:2rem;
a{color:#333;}
.el-icon{
vertical-align:text-top;
}
}
</style>

View File

@@ -83,7 +83,7 @@ export default {
const machineId = computed(() => globalData.value.config.Client.Id); const machineId = computed(() => globalData.value.config.Client.Id);
const showDelay = computed(()=>((globalData.value.config.Running.Tuntap || {Switch:0}).Switch & 2) == 2); const showDelay = computed(()=>((globalData.value.config.Running.Tuntap || {Switch:0}).Switch & 2) == 2);
const handleTuntap = (tuntap) => { const handleTuntap = (tuntap) => {
if(!props.config && machineId.value != tuntap.MachineId){ if(!props.config){
return; return;
} }
const fn = tuntap.running ? stopTuntap (tuntap.MachineId) : runTuntap(tuntap.MachineId); const fn = tuntap.running ? stopTuntap (tuntap.MachineId) : runTuntap(tuntap.MachineId);

View File

@@ -72,7 +72,7 @@ export default {
? 'yellow' :'green' ? 'yellow' :'green'
}) })
const handleUpdate = ()=>{ const handleUpdate = ()=>{
if(!props.config && machineId.value != props.item.MachineId){ if(!props.config){
return; return;
} }
const updateInfo = updater.value.list[props.item.MachineId]; const updateInfo = updater.value.list[props.item.MachineId];

View File

@@ -1,95 +1,39 @@
<template> <template>
<div class="head-wrap"> <div class="head-wrap">
<div class="tools flex"> <div class="tools flex">
<span class="label">服务器 </span><el-select v-model="state.server" placeholder="服务器" style="width:12rem" size="small"> <span class="label">服务器 </span><el-select v-model="state.server" placeholder="服务器" style="width:16rem" size="small">
<el-option v-for="item in state.servers":key="item.Host" :label="item.Name":value="item.Host" ></el-option> <el-option v-for="item in state.servers":key="item.Host" :label="item.Name":value="item.Host" ></el-option>
</el-select> </el-select>
<span class="flex-1"></span> <span class="flex-1"></span>
<el-button size="small" @click="handleEdit">
编辑<el-icon><Edit /></el-icon>
</el-button>
<el-button size="small" @click="handleRefresh"> <el-button size="small" @click="handleRefresh">
刷新(F5)<el-icon><Refresh /></el-icon> 刷新(F5)<el-icon><Refresh /></el-icon>
</el-button> </el-button>
</div> </div>
</div> </div>
<el-dialog v-model="state.show" title="设置" width="260">
<div>
<el-form :model="state.form" :rules="state.rules" label-width="7rem">
<el-form-item label="服务器" prop="host">
<el-input v-model="state.form.host"/>
</el-form-item>
<el-form-item label="中继密钥" prop="relaySecretKey">
<el-input v-model="state.form.relaySecretKey" type="password" show-password maxlength="36" show-word-limit />
</el-form-item>
<el-form-item label="分组号" prop="groupid">
<el-input v-model="state.form.groupid" type="password" show-password maxlength="36" show-word-limit />
</el-form-item>
</el-form>
</div>
<template #footer>
<div class="dialog-footer t-c">
<el-button @click="state.show = false" :loading="state.loading">取消</el-button>
<el-button type="primary" @click="handleSave" :loading="state.loading">确定保存</el-button>
</div>
</template>
</el-dialog>
</template> </template>
<script> <script>
import { injectGlobalData } from '@/provide'; import { injectGlobalData } from '@/provide';
import { reactive, watch } from 'vue'; import { reactive, watch } from 'vue';
import { Edit,Refresh } from '@element-plus/icons-vue'; import { Edit,Refresh } from '@element-plus/icons-vue';
import {save} from '@/apis/net'
import { ElMessage } from 'element-plus';
export default { export default {
components:{Edit,Refresh}, components:{Edit,Refresh},
setup () { setup () {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
const state = reactive({ const state = reactive({
server:"linker.snltty.com:1802", server:"linker.snltty.com:1802",
servers:[], servers:[]
groupid:'snltty',
show:false,
loading:false,
form: {
host: '',
relaySecretKey: '',
groupid: '',
},
rules: {},
}); });
watch(()=>globalData.value.config.Running.Client.Servers,()=>{ watch(()=>globalData.value.config.Running.Client.Servers,()=>{
state.servers = (globalData.value.config.Running.Client.Servers || []).slice(0,1); state.servers = (globalData.value.config.Running.Client.Servers || []).slice(0,1);
state.server = globalData.value.config.Client.Server; state.server = globalData.value.config.Client.Server;
state.groupid = globalData.value.config.Client.GroupId;
}); });
const handleEdit = ()=>{
state.form.host = state.server;
state.form.groupid = state.groupid;
state.form.relaySecretKey = globalData.value.config.Running.Relay.Servers.filter(c=>c.Host == state.form.host)[0].SecretKey;
state.show = true;
}
const handleSave = ()=>{
state.loading = true;
save(state.form).then(()=>{
state.loading = false;
state.show = false;
ElMessage.success('操作成功!');
}).catch(()=>{
state.loading =false;
ElMessage.error('操作失败!');
})
}
const handleRefresh = ()=>{ const handleRefresh = ()=>{
window.location.reload(); window.location.reload();
} }
return { return {
state,handleRefresh,handleEdit,handleSave state,handleRefresh
} }
} }
} }

View File

@@ -7,7 +7,7 @@
<dl> <dl>
<dt class="flex"> <dt class="flex">
<div> <div>
<DeviceName @edit="handleDeviceEdit" :item="item"></DeviceName> <DeviceName :item="item"></DeviceName>
</div> </div>
<div class="flex-1"></div> <div class="flex-1"></div>
<div> <div>
@@ -15,7 +15,7 @@
</div> </div>
</dt> </dt>
<dd class="tuntap"> <dd class="tuntap">
<TuntapShow v-if="tuntap.list[item.MachineId]" @edit="handleTuntapEdit" :item="item"></TuntapShow> <TuntapShow v-if="tuntap.list[item.MachineId]" :item="item"></TuntapShow>
</dd> </dd>
</dl> </dl>
</li> </li>
@@ -29,8 +29,6 @@
@current-change="handlePageChange" @size-change="handlePageSizeChange" :page-sizes="[10, 20, 50, 100,255]" /> @current-change="handlePageChange" @size-change="handlePageSizeChange" :page-sizes="[10, 20, 50, 100,255]" />
</div> </div>
</div> </div>
<TuntapEdit v-if="tuntap.showEdit" v-model="tuntap.showEdit" @change="handleTuntapRefresh"></TuntapEdit>
<DeviceEdit v-if="devices.showDeviceEdit" v-model="devices.showDeviceEdit" @change="handlePageChange" :data="devices.deviceInfo"></DeviceEdit>
</div> </div>
</template> </template>
<script> <script>
@@ -43,11 +41,9 @@ import { provideUpdater } from '../full/devices/updater'
import { StarFilled} from '@element-plus/icons-vue' import { StarFilled} from '@element-plus/icons-vue'
import UpdaterBtn from '../full/devices/UpdaterBtn.vue' import UpdaterBtn from '../full/devices/UpdaterBtn.vue'
import DeviceName from '../full/devices/DeviceName.vue' import DeviceName from '../full/devices/DeviceName.vue'
import DeviceEdit from '../full/devices/DeviceEdit.vue'
import TuntapShow from '../full/devices/TuntapShow.vue'; import TuntapShow from '../full/devices/TuntapShow.vue';
import TuntapEdit from './TuntapEdit.vue'
export default { export default {
components: {StarFilled,UpdaterBtn,DeviceName,DeviceEdit,TuntapShow,TuntapEdit}, components: {StarFilled,UpdaterBtn,DeviceName,TuntapShow},
setup(props) { setup(props) {
const globalData = injectGlobalData(); const globalData = injectGlobalData();
@@ -81,8 +77,8 @@ export default {
}); });
return { return {
state,devices,handleDeviceEdit, machineId, handlePageChange,handlePageSizeChange, handleDel, state,devices, machineId, handlePageChange,handlePageSizeChange,
tuntap,handleTuntapEdit,handleTuntapRefresh tuntap
} }
} }
} }

View File

@@ -1,121 +0,0 @@
<template>
<el-dialog v-model="state.show" :close-on-click-modal="false" title="组网设置" top="1vh" width="270">
<div>
<el-form ref="ruleFormRef" :model="state.ruleForm" :rules="state.rules" label-width="0">
<el-form-item prop="gateway" style="margin-bottom:0">
赐予此设备IP其它设备可通过此IP访问
</el-form-item>
<el-form-item label="" prop="IP" style="margin-bottom:0">
<el-input v-model="state.ruleForm.IP" style="width:14rem" />
<span>/</span>
<el-input @change="handlePrefixLengthChange" v-model="state.ruleForm.PrefixLength" style="width:4rem" />
</el-form-item>
<el-form-item label="" prop="ShowDelay">
<el-checkbox v-model="state.ruleForm.ShowDelay" label="显示延迟" size="large" />
<el-checkbox v-model="state.ruleForm.AutoConnect" label="自动连接?" size="large" />
</el-form-item>
<el-form-item label="" prop="Btns">
<div class="t-c w-100">
<el-button @click="state.show = false">取消</el-button>
<el-button type="primary" @click="handleSave">确认</el-button>
</div>
</el-form-item>
</el-form>
</div>
</el-dialog>
</template>
<script>
import {updateTuntap } from '@/apis/tuntap';
import { injectGlobalData } from '@/provide';
import { ElMessage } from 'element-plus';
import { reactive, ref, watch } from 'vue';
import { useTuntap } from '../full/devices/tuntap';
import { Delete, Plus } from '@element-plus/icons-vue'
export default {
props: ['modelValue'],
emits: ['change','update:modelValue'],
components: {Delete,Plus},
setup(props, { emit }) {
const globalData = injectGlobalData();
const tuntap = useTuntap();
const ruleFormRef = ref(null);
const state = reactive({
show: true,
bufferSize:globalData.value.bufferSize,
ruleForm: {
IP: tuntap.value.current.IP,
LanIPs: tuntap.value.current.LanIPs.slice(0),
Masks: tuntap.value.current.Masks.slice(0),
PrefixLength:tuntap.value.current.PrefixLength || 24,
Gateway: tuntap.value.current.Gateway,
ShowDelay: tuntap.value.current.ShowDelay,
AutoConnect: tuntap.value.current.AutoConnect,
Upgrade: tuntap.value.current.Upgrade,
Forwards:tuntap.value.current.Forwards.length == 0 ? [
{ListenAddr:'0.0.0.0',ListenPort:0,ConnectAddr:'0.0.0.0',ConnectPort:0}
] : tuntap.value.current.Forwards.slice(0)
},
rules: {}
});
watch(() => state.show, (val) => {
if (!val) {
setTimeout(() => {
emit('update:modelValue', val);
}, 300);
}
});
const handlePrefixLengthChange = ()=>{
var value = +state.ruleForm.PrefixLength;
if(value>32 || value<16 || isNaN(value)){
value = 24;
}
state.ruleForm.PrefixLength = value;
}
const handleSave = () => {
const json = JSON.parse(JSON.stringify(tuntap.value.current));
json.IP = state.ruleForm.IP || '0.0.0.0';
const {lanips,masks} = state.ruleForm.LanIPs.reduce((json,ip,index)=>{
if(ip && state.ruleForm.Masks[index]){
json.lanips.push(ip);
json.masks.push(state.ruleForm.Masks[index]);
}
return json;
},{lanips:[],masks:[]});
json.LanIPs = lanips;
json.Masks = masks;
json.PrefixLength = +state.ruleForm.PrefixLength;
json.Gateway = state.ruleForm.Gateway;
json.ShowDelay = state.ruleForm.ShowDelay;
json.AutoConnect = state.ruleForm.AutoConnect;
json.Upgrade = state.ruleForm.Upgrade;
json.Forwards = state.ruleForm.Forwards;
json.Forwards.forEach(c=>{
c.ListenPort=+c.ListenPort;
c.ConnectPort=+c.ConnectPort;
});
updateTuntap(json).then(() => {
state.show = false;
ElMessage.success('已操作!');
emit('change')
}).catch(() => {
ElMessage.error('操作失败!');
});
}
return {
state, ruleFormRef,handlePrefixLengthChange, handleSave
}
}
}
</script>
<style lang="stylus" scoped>
.el-switch.is-disabled{opacity :1;}
.upgrade-wrap{
border:1px solid #ddd;
margin-bottom:2rem
padding:0 0 1rem 0;
}
</style>

View File

@@ -37,12 +37,14 @@ namespace linker.config
object property = item.GetValue(Data); object property = item.GetValue(Data);
MethodInfo method = property.GetType().GetMethod("Load"); MethodInfo method = property.GetType().GetMethod("Load");
MethodInfo method1 = property.GetType().GetMethod("Set");
fsDic.Add(item.Name.ToLower(), new FileReadWrite fsDic.Add(item.Name.ToLower(), new FileReadWrite
{ {
Path = Path.Join(configPath, $"{item.Name.ToLower()}.json"), Path = Path.Join(configPath, $"{item.Name.ToLower()}.json"),
Property = item, Property = item,
PropertyObject = property, PropertyObject = property,
PropertyLoadMethod = method PropertyLoadMethod = method,
PropertySetMethod = method1,
}); });
} }
} }
@@ -89,7 +91,16 @@ namespace linker.config
{ {
continue; continue;
} }
File.WriteAllText(item.Value.Path, item.Value.Property.GetValue(Data).ToJsonFormat()); string text = string.Empty;
if (item.Value.PropertySetMethod != null)
{
text = item.Value.PropertySetMethod.Invoke(item.Value.PropertyObject, new object[] { item.Value.Property.GetValue(Data) }).ToString();
}
else
{
text = item.Value.Property.GetValue(Data).ToJsonFormat();
}
File.WriteAllText(item.Value.Path, text);
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -126,6 +137,7 @@ namespace linker.config
public PropertyInfo Property { get; set; } public PropertyInfo Property { get; set; }
public object PropertyObject { get; set; } public object PropertyObject { get; set; }
public MethodInfo PropertyLoadMethod { get; set; } public MethodInfo PropertyLoadMethod { get; set; }
public MethodInfo PropertySetMethod { get; set; }
} }
@@ -176,7 +188,9 @@ namespace linker.config
} }
public int LoggerSize { get; set; } = 100; public int LoggerSize { get; set; } = 100;
[JsonIgnore]
public string[] IncludePlugins { get; set; } = Array.Empty<string>(); public string[] IncludePlugins { get; set; } = Array.Empty<string>();
[JsonIgnore]
public string[] ExcludePlugins { get; set; } = Array.Empty<string>(); public string[] ExcludePlugins { get; set; } = Array.Empty<string>();

View File

@@ -11,7 +11,7 @@ namespace linker.plugins.capi
public StartupLevel Level => StartupLevel.Normal; public StartupLevel Level => StartupLevel.Normal;
public string Name => "capi"; public string Name => "capi";
public bool Required => false; public bool Required => false;
public string[] Dependent => new string[] {}; public string[] Dependent => new string[] { };
public StartupLoadType LoadType => StartupLoadType.Normal; public StartupLoadType LoadType => StartupLoadType.Normal;
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies) public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
@@ -29,10 +29,11 @@ namespace linker.plugins.capi
clientServer.LoadPlugins(assemblies); clientServer.LoadPlugins(assemblies);
clientServer.Websocket(config.Data.Client.CApi.ApiPort, config.Data.Client.CApi.ApiPassword); clientServer.Websocket(config.Data.Client.CApi.ApiPort, config.Data.Client.CApi.ApiPassword);
LoggerHelper.Instance.Warning($"client api listen:{config.Data.Client.CApi.ApiPort}"); LoggerHelper.Instance.Warning($"client api listen:{config.Data.Client.CApi.ApiPort}");
LoggerHelper.Instance.Warning($"client api password:{config.Data.Client.CApi.ApiPassword}"); if (config.Data.Client.OnlyNode == false)
LoggerHelper.Instance.Warning($"client api password:{config.Data.Client.CApi.ApiPassword}");
} }
if (config.Data.Client.CApi.WebPort > 0) if (config.Data.Client.CApi.WebPort > 0 && config.Data.Client.OnlyNode == false)
{ {
IWebClientServer webServer = serviceProvider.GetService<IWebClientServer>(); IWebClientServer webServer = serviceProvider.GetService<IWebClientServer>();
webServer.Start(config.Data.Client.CApi.WebPort, config.Data.Client.CApi.WebRoot); webServer.Start(config.Data.Client.CApi.WebPort, config.Data.Client.CApi.WebRoot);

View File

@@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
using System.Reflection; using System.Reflection;
using linker.config; using linker.config;
using linker.plugins.client.args; using linker.plugins.client.args;
using System.Net;
using linker.libs.extends;
namespace linker.plugins.client namespace linker.plugins.client
{ {
@@ -20,6 +22,11 @@ namespace linker.plugins.client
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies) public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
{ {
if (string.IsNullOrWhiteSpace(config.Data.Client.Name))
{
config.Data.Client.Name = Dns.GetHostName().SubStr(0, 12);
}
serviceCollection.AddSingleton<SignInArgsTransfer>(); serviceCollection.AddSingleton<SignInArgsTransfer>();
serviceCollection.AddSingleton<ClientSignInState>(); serviceCollection.AddSingleton<ClientSignInState>();

View File

@@ -1,8 +1,10 @@
using linker.config; using linker.config;
using linker.libs;
using linker.libs.extends; using linker.libs.extends;
using LiteDB; using LiteDB;
using MemoryPack; using MemoryPack;
using System.Net; using System.Net;
using System.Text;
namespace linker.client.config namespace linker.client.config
{ {
@@ -41,6 +43,13 @@ namespace linker.config
public sealed partial class ConfigClientInfo public sealed partial class ConfigClientInfo
{ {
private ICrypto crypto;
public ConfigClientInfo()
{
crypto = CryptoFactory.CreateSymmetric(Helper.GlobalString);
}
public bool OnlyNode { get; set; }
#if DEBUG #if DEBUG
public string Server { get; set; } = new IPEndPoint(IPAddress.Loopback, 1802).ToString(); public string Server { get; set; } = new IPEndPoint(IPAddress.Loopback, 1802).ToString();
@@ -84,7 +93,16 @@ namespace linker.config
public ConfigClientInfo Load(string text) public ConfigClientInfo Load(string text)
{ {
return text.DeJson<ConfigClientInfo>(); if (text.Contains("ApiPassword"))
{
return text.DeJson<ConfigClientInfo>();
}
return Encoding.UTF8.GetString(crypto.Decode(Convert.FromBase64String(text)).ToArray()).DeJson<ConfigClientInfo>();
}
public string Set(object obj)
{
return Convert.ToBase64String(crypto.Encode(Encoding.UTF8.GetBytes(obj.ToJson())));
} }
} }

View File

@@ -3,6 +3,8 @@ using linker.libs.api;
using linker.libs.extends; using linker.libs.extends;
using linker.client.config; using linker.client.config;
using linker.plugins.capi; using linker.plugins.capi;
using System.IO.Compression;
using linker.libs;
namespace linker.plugins.config namespace linker.plugins.config
{ {
@@ -74,6 +76,101 @@ namespace linker.plugins.config
return true; return true;
} }
public bool Export(ApiControllerParamsInfo param)
{
try
{
string dirName = "client-node-export";
string rootPath = Path.GetFullPath($"./web/{dirName}");
string zipPath = Path.GetFullPath($"./web/{dirName}.zip");
try
{
File.Delete(zipPath);
}
catch (Exception)
{
}
DeleteDirectory(rootPath);
CopyDirectory(Path.GetFullPath("./"), rootPath, dirName);
DeleteDirectory(Path.Combine(rootPath, $"configs"));
string configPath = Path.Combine(rootPath, $"configs");
Directory.CreateDirectory(configPath);
ConfigClientInfo client = config.Data.Client.ToJson().DeJson<ConfigClientInfo>();
client.Name = string.Empty;
client.Id = string.Empty;
client.CApi.WebPort = 0;
client.OnlyNode = true;
File.WriteAllText(Path.Combine(configPath, $"client.json"), client.Set(client));
ConfigCommonInfo common = config.Data.Common.ToJson().DeJson<ConfigCommonInfo>();
common.Install = true;
common.Modes = ["client"];
File.WriteAllText(Path.Combine(configPath, $"common.json"), common.ToJsonFormat());
ZipFile.CreateFromDirectory(rootPath, zipPath);
Task.Run(async () =>
{
await Task.Delay(5000);
try
{
File.Delete(zipPath);
}
catch (Exception)
{
}
DeleteDirectory(rootPath);
});
}
catch (Exception ex)
{
LoggerHelper.Instance.Error(ex);
}
return true;
}
private void DeleteDirectory(string sourceDir)
{
if (Directory.Exists(sourceDir) == false) return;
foreach (string file in Directory.GetFiles(sourceDir))
{
File.Delete(Path.Combine(sourceDir, file));
}
foreach (string subDir in Directory.GetDirectories(sourceDir))
{
DeleteDirectory(Path.Combine(sourceDir, subDir));
}
Directory.Delete(sourceDir);
}
private void CopyDirectory(string sourceDir, string destDir, string excludeDir)
{
// 创建目标目录
Directory.CreateDirectory(destDir);
// 复制所有文件
foreach (string file in Directory.GetFiles(sourceDir))
{
string fileName = Path.GetFileName(file);
string destFile = Path.Combine(destDir, fileName);
File.Copy(file, destFile, true); // true 表示如果目标文件已存在则覆盖
}
// 递归复制所有子目录
foreach (string subDir in Directory.GetDirectories(sourceDir))
{
if (subDir.EndsWith(excludeDir)) continue;
string subDirName = Path.GetFileName(subDir);
string destSubDir = Path.Combine(destDir, subDirName);
CopyDirectory(subDir, destSubDir, excludeDir);
}
}
} }
public sealed class ConfigInstallInfo public sealed class ConfigInstallInfo

View File

@@ -20,6 +20,10 @@ namespace linker.plugins.logger
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies) public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
{ {
serviceCollection.AddSingleton<LoggerClientApiController>(); serviceCollection.AddSingleton<LoggerClientApiController>();
if (config.Data.Client.OnlyNode)
{
config.Data.Common.LoggerType = libs.LoggerTypes.WARNING;
}
} }
public void AddServer(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies) public void AddServer(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
@@ -30,6 +34,7 @@ namespace linker.plugins.logger
public void UseClient(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies) public void UseClient(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies)
{ {
LoggerClientApiController logger = serviceProvider.GetService<LoggerClientApiController>(); LoggerClientApiController logger = serviceProvider.GetService<LoggerClientApiController>();
} }
public void UseServer(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies) public void UseServer(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies)

View File

@@ -10,6 +10,7 @@ using System.Net;
using System.Reflection; using System.Reflection;
using MemoryPack; using MemoryPack;
using linker.plugins.client; using linker.plugins.client;
using System.Net.Sockets;
namespace linker.plugins.relay namespace linker.plugins.relay
{ {
@@ -180,7 +181,7 @@ namespace linker.plugins.relay
SecretKey = item.SecretKey, SecretKey = item.SecretKey,
Server = server, Server = server,
TransactionId = transactionId, TransactionId = transactionId,
TransportName = transport.Name, TransportName = $"{transport.Name}|{item.Name}",
SSL = item.SSL SSL = item.SSL
}; };
@@ -223,6 +224,7 @@ namespace linker.plugins.relay
try try
{ {
await TestServer(relayInfo);
ITransport _transports = transports.FirstOrDefault(c => c.Name == relayInfo.TransportName); ITransport _transports = transports.FirstOrDefault(c => c.Name == relayInfo.TransportName);
if (_transports != null) if (_transports != null)
{ {
@@ -255,6 +257,29 @@ namespace linker.plugins.relay
} }
return false; return false;
} }
private async Task TestServer(RelayInfo relayInfo)
{
string[] arr = relayInfo.TransportName.Split('|');
relayInfo.TransportName = arr[0];
try
{
Socket socket = new Socket(relayInfo.Server.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(relayInfo.Server).WaitAsync(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false);
socket.SafeClose();
}
catch (Exception)
{
RelayServerInfo server = null;
if (arr.Length > 1)
{
server = running.Data.Relay.Servers.FirstOrDefault(c => c.Name == arr[1]);
}
server ??= running.Data.Relay.Servers.FirstOrDefault();
relayInfo.Server = NetworkHelper.GetEndPoint(server.Host, 3478);
}
}
/// <summary> /// <summary>
/// 回调 /// 回调
/// </summary> /// </summary>

View File

@@ -6,7 +6,6 @@ using System.IO.Compression;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
namespace linker.plugins.updater namespace linker.plugins.updater
{ {
@@ -143,6 +142,7 @@ namespace linker.plugins.updater
using ZipArchive archive = ZipFile.OpenRead("updater.zip"); using ZipArchive archive = ZipFile.OpenRead("updater.zip");
updateInfo.Length = archive.Entries.Sum(c => c.Length); updateInfo.Length = archive.Entries.Sum(c => c.Length);
foreach (ZipArchiveEntry entry in archive.Entries) foreach (ZipArchiveEntry entry in archive.Entries)
{ {
string entryPath = Path.GetFullPath(Path.Join("./", entry.FullName.Substring(entry.FullName.IndexOf('/')))); string entryPath = Path.GetFullPath(Path.Join("./", entry.FullName.Substring(entry.FullName.IndexOf('/'))));

View File

@@ -1,3 +1,3 @@
v1.2.9 v1.2.9
2024-09-03 17:06:02 2024-09-04 17:41:53
1. 选择是否自动同步配置文件 1. 选择是否自动同步配置文件