mirror of
https://github.com/snltty/linker.git
synced 2025-10-18 15:10:43 +08:00
sync
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
|
@@ -7,7 +7,6 @@ using System.Collections.Concurrent;
|
||||
using System.Net.Sockets;
|
||||
using System.Net;
|
||||
using linker.tunnel.wanport;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace linker.tunnel
|
||||
{
|
||||
@@ -186,8 +185,11 @@ namespace linker.tunnel
|
||||
LoggerHelper.Instance.Error($"tunnel {transport.Name} get remote {remoteMachineId} external ip fail ");
|
||||
break;
|
||||
}
|
||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
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()}");
|
||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
LoggerHelper.Instance.Info($"tunnel {transportItem.ToJson()}");
|
||||
|
||||
|
||||
@@ -332,12 +334,12 @@ namespace linker.tunnel
|
||||
|
||||
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}");
|
||||
}
|
||||
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}");
|
||||
}
|
||||
|
||||
@@ -347,7 +349,7 @@ namespace linker.tunnel
|
||||
/// <param name="connection"></param>
|
||||
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}");
|
||||
|
||||
//调用以下别人注册的回调
|
||||
@@ -368,6 +370,7 @@ namespace linker.tunnel
|
||||
}
|
||||
private void OnConnectFail(TunnelTransportInfo tunnelTransportInfo)
|
||||
{
|
||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
LoggerHelper.Instance.Error($"tunnel connect {tunnelTransportInfo.Remote.MachineId} fail");
|
||||
}
|
||||
|
||||
@@ -425,6 +428,7 @@ namespace linker.tunnel
|
||||
{
|
||||
if (AddBackground(remoteMachineId, transactionId) == false)
|
||||
{
|
||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
LoggerHelper.Instance.Error($"tunnel background {remoteMachineId}@{transactionId} already exists");
|
||||
return;
|
||||
}
|
||||
|
@@ -308,6 +308,8 @@ namespace linker.tunnel.transport
|
||||
|
||||
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);
|
||||
|
||||
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
|
||||
LoggerHelper.Instance.Debug($"{Name} connect to {tunnelTransportInfo.Remote.MachineId}->{tunnelTransportInfo.Remote.MachineName} {ep} success");
|
||||
|
||||
TunnelConnectionUdp result = new TunnelConnectionUdp
|
||||
|
@@ -7,3 +7,6 @@ export const getConfig = () => {
|
||||
export const install = (data) => {
|
||||
return sendWebsocketMsg('configclient/install', data);
|
||||
}
|
||||
export const exportConfig = () => {
|
||||
return sendWebsocketMsg('configclient/export');
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
<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" >
|
||||
<template #reference>
|
||||
<a href="javascript:;" title="此设备的管理接口">
|
||||
@@ -9,7 +8,6 @@
|
||||
</a>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
<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">
|
||||
<div class="port-wrap t-c">
|
||||
|
@@ -33,6 +33,7 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-1"></div>
|
||||
<div class="share"><Share :config="config"></Share></div>
|
||||
<div class="api"><Api :config="config"></Api></div>
|
||||
<div class="server" ><Server :config="config"></Server></div>
|
||||
|
||||
@@ -48,12 +49,13 @@
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import Api from './Api.vue'
|
||||
import Server from './Server.vue'
|
||||
import Share from './Share.vue'
|
||||
import { injectGlobalData } from '@/provide';
|
||||
import {Download,Loading,CircleCheck} from '@element-plus/icons-vue'
|
||||
import { ElMessage, ElMessageBox,ElSelect,ElOption } from 'element-plus';
|
||||
import { confirm, exit } from '@/apis/updater';
|
||||
export default {
|
||||
components:{Api,Server,Download,Loading,CircleCheck},
|
||||
components:{Api,Server,Share,Download,Loading,CircleCheck},
|
||||
props:['config'],
|
||||
setup(props) {
|
||||
|
||||
@@ -92,6 +94,7 @@ export default {
|
||||
? 'yellow' :'green'
|
||||
});
|
||||
const handleUpdate = ()=>{
|
||||
if(!props.config) return;
|
||||
if(!updater.value.Version){
|
||||
ElMessage.error('未检测到更新');
|
||||
return;
|
||||
|
63
linker.web/src/components/full/status/Share.vue
Normal file
63
linker.web/src/components/full/status/Share.vue
Normal 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>
|
@@ -83,7 +83,7 @@ export default {
|
||||
const machineId = computed(() => globalData.value.config.Client.Id);
|
||||
const showDelay = computed(()=>((globalData.value.config.Running.Tuntap || {Switch:0}).Switch & 2) == 2);
|
||||
const handleTuntap = (tuntap) => {
|
||||
if(!props.config && machineId.value != tuntap.MachineId){
|
||||
if(!props.config){
|
||||
return;
|
||||
}
|
||||
const fn = tuntap.running ? stopTuntap (tuntap.MachineId) : runTuntap(tuntap.MachineId);
|
||||
|
@@ -72,7 +72,7 @@ export default {
|
||||
? 'yellow' :'green'
|
||||
})
|
||||
const handleUpdate = ()=>{
|
||||
if(!props.config && machineId.value != props.item.MachineId){
|
||||
if(!props.config){
|
||||
return;
|
||||
}
|
||||
const updateInfo = updater.value.list[props.item.MachineId];
|
||||
|
@@ -1,95 +1,39 @@
|
||||
<template>
|
||||
<div class="head-wrap">
|
||||
<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-select>
|
||||
<span class="flex-1"></span>
|
||||
<el-button size="small" @click="handleEdit">
|
||||
编辑<el-icon><Edit /></el-icon>
|
||||
</el-button>
|
||||
<el-button size="small" @click="handleRefresh">
|
||||
刷新(F5)<el-icon><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</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>
|
||||
|
||||
<script>
|
||||
import { injectGlobalData } from '@/provide';
|
||||
import { reactive, watch } from 'vue';
|
||||
import { Edit,Refresh } from '@element-plus/icons-vue';
|
||||
import {save} from '@/apis/net'
|
||||
import { ElMessage } from 'element-plus';
|
||||
export default {
|
||||
components:{Edit,Refresh},
|
||||
setup () {
|
||||
const globalData = injectGlobalData();
|
||||
const state = reactive({
|
||||
server:"linker.snltty.com:1802",
|
||||
servers:[],
|
||||
groupid:'snltty',
|
||||
|
||||
show:false,
|
||||
loading:false,
|
||||
form: {
|
||||
host: '',
|
||||
relaySecretKey: '',
|
||||
groupid: '',
|
||||
},
|
||||
rules: {},
|
||||
servers:[]
|
||||
});
|
||||
watch(()=>globalData.value.config.Running.Client.Servers,()=>{
|
||||
state.servers = (globalData.value.config.Running.Client.Servers || []).slice(0,1);
|
||||
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 = ()=>{
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
return {
|
||||
state,handleRefresh,handleEdit,handleSave
|
||||
state,handleRefresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,7 @@
|
||||
<dl>
|
||||
<dt class="flex">
|
||||
<div>
|
||||
<DeviceName @edit="handleDeviceEdit" :item="item"></DeviceName>
|
||||
<DeviceName :item="item"></DeviceName>
|
||||
</div>
|
||||
<div class="flex-1"></div>
|
||||
<div>
|
||||
@@ -15,7 +15,7 @@
|
||||
</div>
|
||||
</dt>
|
||||
<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>
|
||||
</dl>
|
||||
</li>
|
||||
@@ -29,8 +29,6 @@
|
||||
@current-change="handlePageChange" @size-change="handlePageSizeChange" :page-sizes="[10, 20, 50, 100,255]" />
|
||||
</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>
|
||||
</template>
|
||||
<script>
|
||||
@@ -43,11 +41,9 @@ import { provideUpdater } from '../full/devices/updater'
|
||||
import { StarFilled} from '@element-plus/icons-vue'
|
||||
import UpdaterBtn from '../full/devices/UpdaterBtn.vue'
|
||||
import DeviceName from '../full/devices/DeviceName.vue'
|
||||
import DeviceEdit from '../full/devices/DeviceEdit.vue'
|
||||
import TuntapShow from '../full/devices/TuntapShow.vue';
|
||||
import TuntapEdit from './TuntapEdit.vue'
|
||||
export default {
|
||||
components: {StarFilled,UpdaterBtn,DeviceName,DeviceEdit,TuntapShow,TuntapEdit},
|
||||
components: {StarFilled,UpdaterBtn,DeviceName,TuntapShow},
|
||||
setup(props) {
|
||||
|
||||
const globalData = injectGlobalData();
|
||||
@@ -81,8 +77,8 @@ export default {
|
||||
});
|
||||
|
||||
return {
|
||||
state,devices,handleDeviceEdit, machineId, handlePageChange,handlePageSizeChange, handleDel,
|
||||
tuntap,handleTuntapEdit,handleTuntapRefresh
|
||||
state,devices, machineId, handlePageChange,handlePageSizeChange,
|
||||
tuntap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
@@ -37,12 +37,14 @@ namespace linker.config
|
||||
|
||||
object property = item.GetValue(Data);
|
||||
MethodInfo method = property.GetType().GetMethod("Load");
|
||||
MethodInfo method1 = property.GetType().GetMethod("Set");
|
||||
fsDic.Add(item.Name.ToLower(), new FileReadWrite
|
||||
{
|
||||
Path = Path.Join(configPath, $"{item.Name.ToLower()}.json"),
|
||||
Property = item,
|
||||
PropertyObject = property,
|
||||
PropertyLoadMethod = method
|
||||
PropertyLoadMethod = method,
|
||||
PropertySetMethod = method1,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -89,7 +91,16 @@ namespace linker.config
|
||||
{
|
||||
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)
|
||||
@@ -126,6 +137,7 @@ namespace linker.config
|
||||
public PropertyInfo Property { get; set; }
|
||||
public object PropertyObject { 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;
|
||||
|
||||
[JsonIgnore]
|
||||
public string[] IncludePlugins { get; set; } = Array.Empty<string>();
|
||||
[JsonIgnore]
|
||||
public string[] ExcludePlugins { get; set; } = Array.Empty<string>();
|
||||
|
||||
|
||||
|
@@ -11,7 +11,7 @@ namespace linker.plugins.capi
|
||||
public StartupLevel Level => StartupLevel.Normal;
|
||||
public string Name => "capi";
|
||||
public bool Required => false;
|
||||
public string[] Dependent => new string[] {};
|
||||
public string[] Dependent => new string[] { };
|
||||
public StartupLoadType LoadType => StartupLoadType.Normal;
|
||||
|
||||
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
|
||||
@@ -29,10 +29,11 @@ namespace linker.plugins.capi
|
||||
clientServer.LoadPlugins(assemblies);
|
||||
clientServer.Websocket(config.Data.Client.CApi.ApiPort, config.Data.Client.CApi.ApiPassword);
|
||||
LoggerHelper.Instance.Warning($"client api listen:{config.Data.Client.CApi.ApiPort}");
|
||||
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>();
|
||||
webServer.Start(config.Data.Client.CApi.WebPort, config.Data.Client.CApi.WebRoot);
|
||||
|
@@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Reflection;
|
||||
using linker.config;
|
||||
using linker.plugins.client.args;
|
||||
using System.Net;
|
||||
using linker.libs.extends;
|
||||
|
||||
namespace linker.plugins.client
|
||||
{
|
||||
@@ -20,6 +22,11 @@ namespace linker.plugins.client
|
||||
|
||||
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<ClientSignInState>();
|
||||
|
@@ -1,8 +1,10 @@
|
||||
using linker.config;
|
||||
using linker.libs;
|
||||
using linker.libs.extends;
|
||||
using LiteDB;
|
||||
using MemoryPack;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace linker.client.config
|
||||
{
|
||||
@@ -41,6 +43,13 @@ namespace linker.config
|
||||
|
||||
public sealed partial class ConfigClientInfo
|
||||
{
|
||||
private ICrypto crypto;
|
||||
public ConfigClientInfo()
|
||||
{
|
||||
crypto = CryptoFactory.CreateSymmetric(Helper.GlobalString);
|
||||
}
|
||||
|
||||
public bool OnlyNode { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
public string Server { get; set; } = new IPEndPoint(IPAddress.Loopback, 1802).ToString();
|
||||
@@ -83,9 +92,18 @@ namespace linker.config
|
||||
public string Password { get; set; } = "oeq9tw1o";
|
||||
|
||||
public ConfigClientInfo Load(string text)
|
||||
{
|
||||
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())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,8 @@ using linker.libs.api;
|
||||
using linker.libs.extends;
|
||||
using linker.client.config;
|
||||
using linker.plugins.capi;
|
||||
using System.IO.Compression;
|
||||
using linker.libs;
|
||||
|
||||
namespace linker.plugins.config
|
||||
{
|
||||
@@ -74,6 +76,101 @@ namespace linker.plugins.config
|
||||
|
||||
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
|
||||
|
@@ -20,6 +20,10 @@ namespace linker.plugins.logger
|
||||
public void AddClient(ServiceCollection serviceCollection, FileConfig config, Assembly[] assemblies)
|
||||
{
|
||||
serviceCollection.AddSingleton<LoggerClientApiController>();
|
||||
if (config.Data.Client.OnlyNode)
|
||||
{
|
||||
config.Data.Common.LoggerType = libs.LoggerTypes.WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LoggerClientApiController logger = serviceProvider.GetService<LoggerClientApiController>();
|
||||
|
||||
}
|
||||
|
||||
public void UseServer(ServiceProvider serviceProvider, FileConfig config, Assembly[] assemblies)
|
||||
|
@@ -10,6 +10,7 @@ using System.Net;
|
||||
using System.Reflection;
|
||||
using MemoryPack;
|
||||
using linker.plugins.client;
|
||||
using System.Net.Sockets;
|
||||
|
||||
namespace linker.plugins.relay
|
||||
{
|
||||
@@ -180,7 +181,7 @@ namespace linker.plugins.relay
|
||||
SecretKey = item.SecretKey,
|
||||
Server = server,
|
||||
TransactionId = transactionId,
|
||||
TransportName = transport.Name,
|
||||
TransportName = $"{transport.Name}|{item.Name}",
|
||||
SSL = item.SSL
|
||||
};
|
||||
|
||||
@@ -223,6 +224,7 @@ namespace linker.plugins.relay
|
||||
|
||||
try
|
||||
{
|
||||
await TestServer(relayInfo);
|
||||
ITransport _transports = transports.FirstOrDefault(c => c.Name == relayInfo.TransportName);
|
||||
if (_transports != null)
|
||||
{
|
||||
@@ -255,6 +257,29 @@ namespace linker.plugins.relay
|
||||
}
|
||||
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>
|
||||
|
@@ -6,7 +6,6 @@ using System.IO.Compression;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace linker.plugins.updater
|
||||
{
|
||||
@@ -143,6 +142,7 @@ namespace linker.plugins.updater
|
||||
using ZipArchive archive = ZipFile.OpenRead("updater.zip");
|
||||
updateInfo.Length = archive.Entries.Sum(c => c.Length);
|
||||
|
||||
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
string entryPath = Path.GetFullPath(Path.Join("./", entry.FullName.Substring(entry.FullName.IndexOf('/'))));
|
||||
|
@@ -1,3 +1,3 @@
|
||||
v1.2.9
|
||||
2024-09-03 17:06:02
|
||||
2024-09-04 17:41:53
|
||||
1. 选择是否自动同步配置文件
|
Reference in New Issue
Block a user