服务器穿透cdkey

This commit is contained in:
snltty
2025-08-25 16:21:20 +08:00
parent 60fe0591ab
commit b90a30c8d1
31 changed files with 166 additions and 69 deletions

View File

@@ -37,7 +37,7 @@ jobs:
release_name: v1.9.0.${{ steps.date.outputs.today }}
draft: false
prerelease: false
body: "1. 一些累计更新\r\n2. 修复一些APP问题\r\n3. 增加一些数据统计\r\n4. 可选关闭信标服务"
body: "1. 一些累计更新\r\n2. 服务器转发多节点\r\n3. 一些代码优化\r\n4. 修复一些错误\r\n5. 其它一些小改变"
- name: publish projects
run: ./publish.bat "C:\\Android\\android-sdk"
- name: upload-win-x86-oss

View File

@@ -44,7 +44,10 @@ slug: /p2p/relay
"MaxGbTotalLastBytes": 0,
//流量统计月度0即可
"MaxGbTotalMonth": 0,
//是否公开本节点,公开则主服务器上的所有客户端可用本节点,不公开则中继认证通过时可用
//是否公开本节点
// 1、公开时则所有客户端可用本节点受MaxConnection、MaxBandwidth、MaxBandwidthTotal、MaxGbTotal 限制
// 2、未公开时仅超级管理、白名单内、cdkey 可使用
// 3、超级管理、白名单内 无任何限制
"Public": false,
//主服务器地址

View File

@@ -1,11 +1,12 @@
using System;
using System.IO;
using System.Net;
namespace linker.libs
{
public static class FireWallHelper
{
public static void Write(string fileName)
public static void WriteAny(string fileName)
{
if (OperatingSystem.IsWindows())
{
@@ -16,6 +17,13 @@ namespace linker.libs
Linux(fileName);
}
}
public static void WriteIcmp(string fileName, IPAddress ip, byte prefixLength)
{
if (OperatingSystem.IsWindows())
{
Windows(fileName, ip, prefixLength);
}
}
private static void Linux(string fileName)
{
@@ -40,10 +48,22 @@ namespace linker.libs
{
string name = Path.GetFileNameWithoutExtension(fileName);
CommandHelper.Windows(string.Empty, new string[] {
$"netsh advfirewall firewall delete rule name=\"{name}\"",
$"netsh advfirewall firewall add rule name=\"{name}\" dir=in action=allow program=\"{fileName}\" protocol=tcp enable=yes",
$"netsh advfirewall firewall add rule name=\"{name}\" dir=in action=allow program=\"{fileName}\" protocol=udp enable=yes",
$"netsh advfirewall firewall add rule name=\"{name}\" dir=in action=allow program=\"{fileName}\" protocol=icmpv4 enable=yes",
$"netsh advfirewall firewall delete rule name=\"{name}-any\"",
$"netsh advfirewall firewall add rule name=\"{name}-any\" dir=in action=allow program=\"{fileName}\" enable=yes"
});
}
catch (Exception)
{
}
}
private static void Windows(string fileName, IPAddress ip, byte prefixLength)
{
try
{
string name = Path.GetFileNameWithoutExtension(fileName);
CommandHelper.Windows(string.Empty, new string[] {
$"netsh advfirewall firewall delete rule name=\"{name}-icmp\"",
$"netsh advfirewall firewall add rule name=\"{name}-icmp\" dir=in action=allow protocol=icmpv4 remoteip={ip}/{prefixLength} enable=yes",
});
}
catch (Exception)

View File

@@ -1,5 +1,6 @@
using linker.libs.web;
using linker.messenger.relay.server;
using linker.messenger.sforward.server;
using Microsoft.Extensions.DependencyInjection;
namespace linker.messenger.cdkey
{
@@ -25,6 +26,7 @@ namespace linker.messenger.cdkey
serviceCollection.AddSingleton<CdkeyServerMessenger>();
serviceCollection.AddSingleton<IRelayServerCdkeyStore, RelayCdkeyStore>();
serviceCollection.AddSingleton<ISForwardServerCdkeyStore, SForwardCdkeyStore>();
return serviceCollection;
}

View File

@@ -43,6 +43,7 @@ namespace linker.messenger.cdkey
/// <param name="userid"></param>
/// <returns></returns>
public Task<List<CdkeyStoreInfo>> GetAvailable(string userid, string type);
public Task<List<CdkeyStoreInfo>> GetAvailable(string userid, string type,string value);
/// <summary>
/// 获取CDKEY列表
/// </summary>
@@ -202,6 +203,7 @@ namespace linker.messenger.cdkey
/// 支付金额
/// </summary>
public double PayPrice { get; set; }
/// <summary>
/// 备注
/// </summary>
@@ -219,6 +221,8 @@ namespace linker.messenger.cdkey
/// 已删除
/// </summary>
public bool Deleted { get; set; }
public string[] Values { get; set; } = [];
}
/// <summary>
@@ -290,5 +294,7 @@ namespace linker.messenger.cdkey
public int Count { get; set; }
public string Type { get; set; }
public string[] Values { get; set; } = [];
}
}

View File

@@ -9,10 +9,14 @@ namespace linker.messenger.cdkey
{
this.cdkeyServerStore = cdkeyServerStore;
}
public async Task<List<SForwardCdkeyInfo>> GetAvailable(string userid,string target)
public async Task<List<SForwardCdkeyInfo>> GetAvailable(string userid)
{
return (await cdkeyServerStore.GetAvailable(userid, "SForward")).Select(c => new SForwardCdkeyInfo { Bandwidth = c.Bandwidth, Id = c.Id, LastBytes = c.LastBytes }).ToList();
}
public async Task<List<SForwardCdkeyInfo>> GetAvailable(string userid, string target)
{
return (await cdkeyServerStore.GetAvailable(userid, "SForward", target)).Select(c => new SForwardCdkeyInfo { Bandwidth = c.Bandwidth, Id = c.Id, LastBytes = c.LastBytes }).ToList();
}
public async Task<Dictionary<int, long>> GetLastBytes(List<int> ids)
{

View File

@@ -67,7 +67,7 @@ namespace linker.messenger.relay.client
{
TimerHelper.SetIntervalLong(async () =>
{
if (lastTicksManager.DiffLessEqual(3000) || Nodes.Count <= 0)
if ((lastTicksManager.DiffLessEqual(3000) || Nodes.Count <= 0) && signInClientState.Connected)
{
await TaskRelay().ConfigureAwait(false);
}

View File

@@ -111,8 +111,11 @@ namespace linker.messenger.serializer.memorypack
[MemoryPackInclude]
string Type => info.Type;
[MemoryPackInclude]
string[] Values => info.Values;
[MemoryPackConstructor]
SerializableCdkeyStoreInfo(double bandwidth, long lastBytes, int id, string userid, DateTime addTime, DateTime startTime, DateTime endTime, DateTime useTime, long maxBytes, double costPrice, double price, double userPrice, double payPrice, string remark, string orderId, string contact, bool deleted, string type)
SerializableCdkeyStoreInfo(double bandwidth, long lastBytes, int id, string userid, DateTime addTime, DateTime startTime, DateTime endTime, DateTime useTime, long maxBytes, double costPrice, double price, double userPrice, double payPrice, string remark, string orderId, string contact, bool deleted, string type, string[] values)
{
var info = new CdkeyStoreInfo
{
@@ -133,7 +136,8 @@ namespace linker.messenger.serializer.memorypack
OrderId = orderId,
Contact = contact,
Deleted = deleted,
Type = type
Type = type,
Values = values
};
this.info = info;
}
@@ -574,10 +578,11 @@ namespace linker.messenger.serializer.memorypack
int Count => info.Count;
[MemoryPackInclude]
string Type => info.Type;
[MemoryPackInclude]
string[] Values => info.Values;
[MemoryPackConstructor]
SerializableCdkeyOrderInfo(int gb, int speed, string time, string widgetUserId, string orderId, string contact, double costPrice, double price, double userPrice, double payPrice, int count, string type)
SerializableCdkeyOrderInfo(int gb, int speed, string time, string widgetUserId, string orderId, string contact, double costPrice, double price, double userPrice, double payPrice, int count, string type, string[] values)
{
var info = new CdkeyOrderInfo
{
@@ -592,7 +597,8 @@ namespace linker.messenger.serializer.memorypack
UserPrice = userPrice,
PayPrice = payPrice,
Count = count,
Type = type
Type = type,
Values= values
};
this.info = info;
}

View File

@@ -2,6 +2,7 @@
using linker.libs.timer;
using linker.messenger.sforward.server;
using linker.messenger.signin;
using linker.plugins.sforward.messenger;
using System.Net;
using System.Net.NetworkInformation;
@@ -45,18 +46,19 @@ namespace linker.messenger.sforward.client
{
try
{
var tasks = Nodes.Select(async (c) =>
var resp = await messengerSender.SendReply(new MessageRequestWrap
{
c.Address = c.Address == null || c.Address.Equals(IPAddress.Any) ? signInClientState.Connection.Address.Address : c.Address;
using Ping ping = new Ping();
var resp = await ping.SendPingAsync(c.Address, 1000);
c.Delay = resp.Status == IPStatus.Success ? (int)resp.RoundtripTime : -1;
Connection = signInClientState.Connection,
MessengerId = (ushort)SForwardMessengerIds.Nodes
});
await Task.WhenAll(tasks).ConfigureAwait(false);
Nodes = serializer.Deserialize<List<SForwardServerNodeReportInfo>>(resp.Data.Span);
}
catch (Exception)
catch (Exception ex)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
}
}
private async Task PingNodes()
@@ -73,8 +75,12 @@ namespace linker.messenger.sforward.client
});
await Task.WhenAll(tasks).ConfigureAwait(false);
}
catch (Exception)
catch (Exception ex)
{
if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG)
{
LoggerHelper.Instance.Error(ex);
}
}
}
@@ -83,7 +89,7 @@ namespace linker.messenger.sforward.client
{
TimerHelper.SetIntervalLong(async () =>
{
if (lastTicksManager.DiffLessEqual(3000) || Nodes.Count <= 0)
if ((lastTicksManager.DiffLessEqual(3000) || Nodes.Count <= 0) && signInClientState.Connected)
{
await TaskNodes().ConfigureAwait(false);
await PingNodes().ConfigureAwait(false);

View File

@@ -428,7 +428,7 @@ namespace linker.plugins.sforward.messenger
{
Add((SForwardAddInfo)sForwardAddInfo, sForwardAddInfo.MachineId, sForwardAddInfo.GroupId, result, sForwardAddInfo.Validated, sForwardAddInfo.Cdkey);
}
private bool PortRange(string str, out int min, out int max)
private static bool PortRange(string str, out int min, out int max)
{
min = 0; max = 0;
string[] arr = str.Split('/');

View File

@@ -2,6 +2,7 @@
{
public interface ISForwardServerCdkeyStore
{
public Task<List<SForwardCdkeyInfo>> GetAvailable(string userid);
public Task<List<SForwardCdkeyInfo>> GetAvailable(string userid,string target);
/// <summary>
@@ -20,6 +21,10 @@
public sealed class SForwardServerCdkeyStore : ISForwardServerCdkeyStore
{
public async Task<List<SForwardCdkeyInfo>> GetAvailable(string userid)
{
return await Task.FromResult(new List<SForwardCdkeyInfo>());
}
public async Task<List<SForwardCdkeyInfo>> GetAvailable(string userid, string target)
{
return await Task.FromResult(new List<SForwardCdkeyInfo>());

View File

@@ -111,11 +111,13 @@ namespace linker.messenger.sforward.server
List<string> sforward = await sForwardServerWhiteListStore.Get(from.UserId);
string target = string.IsNullOrWhiteSpace(info.Domain) ? info.RemotePort.ToString() : info.Domain;
info.Validated = from.Super && (sforward.Contains($"sfp->{target}") || sforward.Contains($"sfp->*")) && (sforward.Contains(info.NodeId) || sforward.Contains($"*"));
info.Validated = from.Super || (sforward.Contains($"sfp->{target}") || sforward.Contains($"sfp->*")) && (sforward.Contains(info.NodeId) || sforward.Contains($"*"));
if (info.Validated == false)
{
info.Cdkey = (await sForwardServerCdkeyStore.GetAvailable(from.UserId, target).ConfigureAwait(false)).Select(c => new SForwardCdkeyInfo { Bandwidth = c.Bandwidth, Id = c.Id, LastBytes = c.LastBytes }).ToList();
var cdkeys = await sForwardServerCdkeyStore.GetAvailable(from.UserId, $"sfp->{target}").ConfigureAwait(false);
var anyCdkeys = await sForwardServerCdkeyStore.GetAvailable(from.UserId, $"sfp->*").ConfigureAwait(false);
info.Cdkey = cdkeys.Concat(anyCdkeys).Select(c => new SForwardCdkeyInfo { Bandwidth = c.Bandwidth, Id = c.Id, LastBytes = c.LastBytes }).ToList();
if (info.Cdkey.Count <= 0 && node.Public == false)
{

View File

@@ -152,7 +152,8 @@ namespace linker.messenger.store.file.cdkey
Contact = order.Contact,
OrderId = order.OrderId,
PayPrice = order.PayPrice,
UserPrice = order.UserPrice
UserPrice = order.UserPrice,
Values = order.Values ?? [],
};
liteCollection.Insert(store);
return await Task.FromResult(string.Empty).ConfigureAwait(false);
@@ -179,9 +180,13 @@ namespace linker.messenger.store.file.cdkey
public async Task<List<CdkeyStoreInfo>> GetAvailable(string userid, string type)
{
if (string.IsNullOrWhiteSpace(userid) || string.IsNullOrWhiteSpace(type)) return [];
return await Task.FromResult(liteCollection.Find(x => x.UserId == userid && x.Type == type && x.LastBytes > 0 && x.StartTime <= DateTime.Now && x.EndTime >= DateTime.Now && x.Deleted == false).ToList()).ConfigureAwait(false);
}
public async Task<List<CdkeyStoreInfo>> GetAvailable(string userid, string type, string value)
{
if (string.IsNullOrWhiteSpace(userid) || string.IsNullOrWhiteSpace(type)) return [];
return await Task.FromResult(liteCollection.Find(x => x.UserId == userid && x.Type == type && x.Values.Contains(value) && x.LastBytes > 0 && x.StartTime <= DateTime.Now && x.EndTime >= DateTime.Now && x.Deleted == false).ToList()).ConfigureAwait(false);
}
public async Task<List<CdkeyStoreInfo>> Get(List<int> ids)
{
return await Task.FromResult(liteCollection.Find(x => ids.Contains(x.Id)).ToList()).ConfigureAwait(false);

View File

@@ -56,6 +56,7 @@ namespace linker.messenger.tuntap
private void SignInSuccess(int times)
{
_ = CheckDevice();
FireWallHelper.WriteIcmp(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName, tuntapConfigTransfer.Info.IP, tuntapConfigTransfer.Info.PrefixLength);
}
private void Update()
{

View File

@@ -149,7 +149,7 @@ namespace linker.tunnel.transport
TaskCompletionSource<IPEndPoint> taskCompletionSource = new TaskCompletionSource<IPEndPoint>(TaskCreationOptions.RunContinuationsAsynchronously);
//监听连接
Socket remoteUdp = BindListen(tunnelTransportInfo.Local.Local, taskCompletionSource);
Socket remoteUdp = BindListen(tunnelTransportInfo.Local.Local, taskCompletionSource, tunnelTransportInfo.RemoteEndPoints.Select(c=>c.Address).ToList());
//给对方发送简单消息
foreach (IPEndPoint ep in tunnelTransportInfo.RemoteEndPoints)
@@ -214,7 +214,7 @@ namespace linker.tunnel.transport
/// <param name="local"></param>
/// <param name="tcs"></param>
/// <returns></returns>
private Socket BindListen(IPEndPoint local, TaskCompletionSource<IPEndPoint> tcs)
private Socket BindListen(IPEndPoint local, TaskCompletionSource<IPEndPoint> tcs,List<IPAddress> ips)
{
local = new IPEndPoint(IPAddress.IPv6Any, local.Port);
Socket socket = new Socket(local.AddressFamily, SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
@@ -224,9 +224,21 @@ namespace linker.tunnel.transport
TimerHelper.Async(async () =>
{
SocketReceiveFromResult result = await socket.ReceiveFromAsync(new byte[1024], new IPEndPoint(IPAddress.IPv6Any, 0)).ConfigureAwait(false);
await socket.SendToAsync(endBytes, result.RemoteEndPoint).ConfigureAwait(false);
tcs.TrySetResult(result.RemoteEndPoint as IPEndPoint);
byte[] buffer = new byte[1024];
while (true)
{
SocketReceiveFromResult result = await socket.ReceiveFromAsync(buffer, new IPEndPoint(IPAddress.IPv6Any, 0)).ConfigureAwait(false);
if (ips.Contains((result.RemoteEndPoint as IPEndPoint).Address))
{
await socket.SendToAsync(endBytes, result.RemoteEndPoint).ConfigureAwait(false);
tcs.TrySetResult(result.RemoteEndPoint as IPEndPoint);
break;
}
else
{
LoggerHelper.Instance.Warning($"{Name} connect recv from {result.RemoteEndPoint} {buffer.AsMemory(0, result.ReceivedBytes).GetString()}");
}
}
});
return socket;
}

View File

@@ -1,5 +1,5 @@
<template>
<div :class="{phone:globalData.isPhone}">
<div class="app-inner absolute" :class="{phone:globalData.isPhone}">
<el-config-provider :locale="locale">
<template v-if="configed">
<router-view />
@@ -32,6 +32,8 @@ export default {
}
</script>
<style lang="stylus" scoped>
html.dark .app-inner{
background: radial-gradient(circle at 15% 50%, rgba(34, 197, 94, 0.4) 0px, transparent 0px) 0px 0px / 100% 100%, radial-gradient(circle at 85% 50%, rgba(22, 163, 74, 0.4) 0px, transparent 0px) 0px 0px / 100% 100%, linear-gradient(90deg, transparent 0%, rgba(34, 197, 94, 0.15) 15%, rgba(34, 197, 94, 0.25) 50%, rgba(22, 163, 74, 0.15) 85%, transparent 100%) 0px 50% / 100% 4px;
}
</style>

View File

@@ -170,11 +170,10 @@ table {
html {
font-size: 10px;
/* background-color: #282c34; */
background-color: #f4f4f4;
background-color: #f5f5f5;
}
html.dark{
background-color: #282c34;
background-color: #000;
}
html.dark .el-switch__core .el-switch__action{
background-color: #ccc;

View File

@@ -230,6 +230,8 @@ export default {
'server.cdkeyOrderId': 'OrderNo',
'server.cdkeyContact': 'Email',
'server.cdkeyRemark': 'Remark',
'server.cdkeyValuesRelay': 'Values',
'server.cdkeyValuesSForward': 'Port/Doamin',
'server.cdkeyAddTime': 'Add',
'server.cdkeyStartTime': 'Start',

View File

@@ -325,6 +325,8 @@ export default {
'server.cdkeyOrderId': '订单号',
'server.cdkeyContact': '邮箱',
'server.cdkeyRemark': '备注',
'server.cdkeyValuesRelay': '中继值',
'server.cdkeyValuesSForward': '端口/域名',
'server.cdkeyAddTime': '添加',
'server.cdkeyStartTime': '开始',

View File

@@ -51,6 +51,9 @@
</el-col>
</el-row>
</el-form-item>
<el-form-item v-if="state.prefix" :label="$t(`server.cdkeyValues${state.ruleForm.Type}`)" prop="values">
<el-input v-trim show-word-limit v-model="state.values" />
</el-form-item>
<el-form-item></el-form-item>
<el-form-item label="" prop="Btns">
<div class="t-c w-100">
@@ -70,12 +73,14 @@ import { useI18n } from 'vue-i18n';
import moment from "moment";
import { cdkeyAdd } from '@/apis/cdkey';
export default {
props: ['modelValue','type'],
props: ['modelValue','type','prefix'],
emits: ['update:modelValue','success'],
setup(props,{emit}) {
const {t} = useI18n();
const state = reactive({
show:true,
prefix:props.prefix,
values:'',
ruleForm:{
UserId:'',
Bandwidth:1,
@@ -96,6 +101,7 @@ export default {
Remark:'hand',
Contact:'',
Type:props.type,
Values:[],
},
rules:{
UserId: [{ required: true, message: "required", trigger: "blur" }],
@@ -120,6 +126,7 @@ export default {
const end = new Date(date.getFullYear()+json.Year,date.getMonth()+json.Month,date.getDate()+json.Day,date.getHours()+json.Hour,date.getMinutes()+json.Min,date.getSeconds()+json.Sec);
json.EndTime = moment(end).format("YYYY-MM-DD HH:mm:ss");
json.MaxBytes = json.G*1024*1024*1024 + json.M*1024*1024 + json.K*1024 + json.B;
json.Values = state.values.trim().split(',').filter(v=>v).map(c=>`${state.prefix}${c}`);
cdkeyAdd(json).then(()=>{
ElMessage.success(t('common.oper'));
state.show = false;

View File

@@ -1,8 +1,8 @@
<template>
<a @click="state.showMy = true" href="javascript:;" class="mgr-1 a-line">{{$t('server.myCdkey')}}</a>
<a v-if="state.super && hasCdkey" @click="state.showManager = true" href="javascript:;" class="mgr-1 a-line">{{$t('server.cdkey')}}</a>
<Manager :type="state.type" v-if="state.showManager" v-model="state.showManager" />
<My :type="state.type" v-if="state.showMy" v-model="state.showMy" />
<Manager :type="state.type" v-if="state.showManager" v-model="state.showManager" :prefix="state.prefix" />
<My :type="state.type" v-if="state.showMy" v-model="state.showMy" :prefix="state.prefix" />
</template>
<script>
@@ -11,7 +11,7 @@ import { computed, onMounted, reactive } from 'vue';
import Manager from './Manager.vue'
import My from './My.vue'
export default {
props:['type'],
props:['type','prefix'],
components:{Manager,My},
setup (props) {
@@ -21,7 +21,8 @@ export default {
super:computed(()=>globalData.value.signin.Super),
showManager:false,
showMy:false,
type:props.type
type:props.type ,
prefix:props.prefix ,
});
return {state,hasCdkey}

View File

@@ -44,10 +44,15 @@
<el-table-column prop="OrderId" :label="`${$t('server.cdkeyOrder')}`" width="180">
<template #default="scope">
<p>{{ scope.row.OrderId }}</p>
<p><strong>{{ scope.row.Values.map(c=>c.replace(state.prefix,'')).join(',') }}</strong></p>
</template>
</el-table-column>
<el-table-column prop="Remark" :label="$t('server.cdkeyRemark')">
<template #default="scope">
<p>{{ scope.row.Remark }}</p>
<p>{{ scope.row.Contact }}</p>
</template>
</el-table-column>
<el-table-column prop="Remark" :label="$t('server.cdkeyRemark')"></el-table-column>
<el-table-column prop="EndTime" :label="`${$t('server.cdkeyEndTime')}`" width="140" sortable="custom">
</el-table-column>
<el-table-column prop="UseTime" :label="`${$t('server.cdkeyUseTime')}`" width="140" sortable="custom">
@@ -79,7 +84,7 @@
</div>
</div>
</el-dialog>
<Add :type="state.page.Type" v-if="state.showAdd" v-model="state.showAdd" @success="handleSearch"></Add>
<Add :type="state.page.Type" :prefix="state.prefix" v-if="state.showAdd" v-model="state.showAdd" @success="handleSearch"></Add>
<Test v-if="state.showTest" v-model="state.showTest"></Test>
</template>
@@ -93,7 +98,7 @@ import Flags from './Flags.vue';
import Add from './Add.vue';
import Test from './Test.vue';
export default {
props: ['modelValue','type'],
props: ['modelValue','type','prefix'],
emits: ['update:modelValue'],
components:{Delete,Plus,Search ,Flags,Add,Test,Warning},
setup(props,{emit}) {
@@ -120,7 +125,8 @@ export default {
},
show:true,
showAdd:false,
showTest:false
showTest:false,
prefix:props.prefix
});
watch(() => state.show, (val) => {
if (!val) {

View File

@@ -1,12 +1,13 @@
<template>
<el-form-item :label="$t('server.sforward')" v-if="state.super && hasWhiteList">
<el-form-item :label="$t('server.sforward')">
<div>
<div class="flex">
<a href="javascript:;" @click="state.showModes = true" class="mgr-1 delay a-line" :class="{red:state.nodes.length==0,green:state.nodes.length>0}">
{{$t('server.sforwardNodes')}} : {{state.nodes.length}}
</a>
<WhiteList type="SForward" prefix="sfp->" :sprefix="true"></WhiteList>
<WhiteList type="SForward" prefix="sfp->" v-if="state.super && hasWhiteList"></WhiteList>
<Nodes v-if="state.showModes" v-model="state.showModes" :data="state.nodes"></Nodes>
<Cdkey type="SForward" prefix="sfp->"></Cdkey>
</div>
</div>
</el-form-item>

View File

@@ -11,9 +11,10 @@
<span>({{ scope.row.Domain || scope.row.Address }})</span>
</p>
<p class="flex">
<el-checkbox class="mgr-p6" v-model="scope.row.Sync2Server" disabled size="small" @click="handleSync2Server(scope.row)">{{ $t('server.sforwardSync2Server') }}</el-checkbox>
<el-checkbox v-if="state.super" class="mgr-p6" v-model="scope.row.Sync2Server" disabled size="small" @click="handleSync2Server(scope.row)">{{ $t('server.sforwardSync2Server') }}</el-checkbox>
<span class="flex-1"></span>
<a href="javascript:;" class="a-line a-edit" @click="handleUpdate(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
<a v-if="state.super" href="javascript:;" class="a-line a-edit" @click="handleUpdate(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
<a v-else href="javascript:;" class="a-line a-edit"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
</p>
</div>
</template>
@@ -51,13 +52,13 @@
<p><strong>{{scope.row.BandwidthRatio}}mbps</strong></p>
</template>
</el-table-column>
<el-table-column property="Public" :label="$t('server.sforwardOper')" width="60">
<el-table-column v-if="state.super" property="Public" :label="$t('server.sforwardOper')" width="60">
<template #default="scope">
<p>
<a v-if="state.super" href="javascript:;" class="a-line" @click="handleExit(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ $t('server.sforwardExit') }}</a>
<a href="javascript:;" class="a-line" @click="handleExit(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ $t('server.sforwardExit') }}</a>
</p>
<p>
<a v-if="state.super" href="javascript:;" class="a-line" @click="handleEdit(scope.row)"><el-icon><Edit /></el-icon>{{ $t('server.sforwardEdit') }}</a>
<a href="javascript:;" class="a-line" @click="handleEdit(scope.row)"><el-icon><Edit /></el-icon>{{ $t('server.sforwardEdit') }}</a>
</p>
</template>
</el-table-column>

View File

@@ -17,7 +17,7 @@
</a>
</p>
<p class="flex">
<el-checkbox class="mgr-p6" v-model="scope.row.Sync2Server" disabled size="small" @click="handleSync2Server(scope.row)">{{ $t('server.relaySync2Server') }}</el-checkbox>
<el-checkbox v-if="state.super" class="mgr-p6" v-model="scope.row.Sync2Server" disabled size="small" @click="handleSync2Server(scope.row)">{{ $t('server.relaySync2Server') }}</el-checkbox>
<template v-if="(scope.row.AllowProtocol & 1) == 1">
<template v-if="state.syncData.Key == scope.row.Id && state.syncData.Value == 1">
<el-checkbox class="mgr-p6" size="small" disabled checked>{{ $t('server.relayDefault') }}TCP</el-checkbox>
@@ -35,7 +35,8 @@
</template>
</template>
<span class="flex-1"></span>
<a href="javascript:;" class="a-line a-edit" @click="handleUpdate(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
<a v-if="state.super" href="javascript:;" class="a-line a-edit" @click="handleUpdate(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
<a v-else href="javascript:;" class="a-line a-edit"><el-icon><Refresh /></el-icon>{{ scope.row.Version }}</a>
</p>
</div>
</template>
@@ -73,13 +74,13 @@
<p><strong>{{scope.row.ConnectionRatio}}</strong></p>
</template>
</el-table-column>
<el-table-column property="Public" :label="$t('server.relayOper')" width="60">
<el-table-column v-if="state.super" property="Public" :label="$t('server.relayOper')" width="60">
<template #default="scope">
<p>
<a v-if="state.super" href="javascript:;" class="a-line" @click="handleExit(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ $t('server.relayExit') }}</a>
<a href="javascript:;" class="a-line" @click="handleExit(scope.row.Id)"><el-icon><Refresh /></el-icon>{{ $t('server.relayExit') }}</a>
</p>
<p>
<a v-if="state.super" href="javascript:;" class="a-line" @click="handleEdit(scope.row)"><el-icon><Edit /></el-icon>{{ $t('server.relayEdit') }}</a>
<a href="javascript:;" class="a-line" @click="handleEdit(scope.row)"><el-icon><Edit /></el-icon>{{ $t('server.relayEdit') }}</a>
</p>
</template>
</el-table-column>

View File

@@ -79,9 +79,11 @@ export default {
left:50%;
top:50%;
transform:translateX(-50%) translateY(-50%);
box-shadow: 0 8px 50px rgba(0,0,0, 0.1);
}
html.dark .app-wrap{
background-color:#141414;
border-color:#575c61;
box-shadow: 0 8px 50px rgba(34, 197, 94, 0.1);
}
</style>

View File

@@ -63,7 +63,6 @@ html.dark .head{background-color:#242526;border-color:#575c61;}
.head{
background-color:#f6f8fa;
border-bottom:1px solid #d0d7de;
box-shadow:1px 1px 4px rgba(0,0,0,0.05);
height:5rem;
line-height:5rem;
.logo{

View File

@@ -15,7 +15,7 @@ namespace linker
#if DEBUG
#else
//添加防火墙不添加ICMP
linker.libs.FireWallHelper.Write(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
linker.libs.FireWallHelper.WriteAny(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
#endif
//全局异常
AppDomain.CurrentDomain.UnhandledException += (a, b) =>

View File

@@ -21,9 +21,10 @@
<Authors>snltty</Authors>
<Company>snltty</Company>
<Description>1. 一些累计更新
2. 修复一些APP问题
3. 增加一些数据统计
4. 可选关闭信标服务</Description>
2. 服务器转发多节点
3. 一些代码优化
4. 修复一些错误
5. 其它一些小改变</Description>
<Copyright>snltty</Copyright>
<PackageProjectUrl>https://github.com/snltty/linker</PackageProjectUrl>
<RepositoryUrl>https://github.com/snltty/linker</RepositoryUrl>

View File

@@ -1,6 +1,7 @@
v1.9.0
2025-08-20 17:49:08
2025-08-25 16:21:19
1. 一些累计更新
2. 修复一些APP问题
3. 增加一些数据统计
4. 可选关闭信标服务
2. 服务器转发多节点
3. 一些代码优化
4. 修复一些错误
5. 其它一些小改变