Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions NodePass.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
57D9675B2F23B08C00AE3CCB /* ConnectionMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D967582F23B08C00AE3CCB /* ConnectionMode.swift */; };
57D9675C2F23B08C00AE3CCB /* LoadBalancingStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D967592F23B08C00AE3CCB /* LoadBalancingStrategy.swift */; };
57D9675D2F23B08C00AE3CCB /* TLSMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D9675A2F23B08C00AE3CCB /* TLSMode.swift */; };
57D967602F24B72B00AE3CCB /* NetworkTuningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D9675E2F24B72B00AE3CCB /* NetworkTuningView.swift */; };
57D967612F24B72B00AE3CCB /* ProtocolControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57D9675F2F24B72B00AE3CCB /* ProtocolControlView.swift */; };
CB1C10DF2F2495ED00B74CD8 /* TrafficBlockingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1C10DE2F2495ED00B74CD8 /* TrafficBlockingView.swift */; };
CB1E97FF2E2262F600A175FD /* NATPassthroughDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1E97FE2E2262E700A175FD /* NATPassthroughDetailView.swift */; };
CB1E98012E226C1E00A175FD /* DirectForwardDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB1E98002E226C0F00A175FD /* DirectForwardDetailView.swift */; };
Expand Down Expand Up @@ -165,6 +167,8 @@
57D967582F23B08C00AE3CCB /* ConnectionMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionMode.swift; sourceTree = "<group>"; };
57D967592F23B08C00AE3CCB /* LoadBalancingStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadBalancingStrategy.swift; sourceTree = "<group>"; };
57D9675A2F23B08C00AE3CCB /* TLSMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TLSMode.swift; sourceTree = "<group>"; };
57D9675E2F24B72B00AE3CCB /* NetworkTuningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkTuningView.swift; sourceTree = "<group>"; };
57D9675F2F24B72B00AE3CCB /* ProtocolControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProtocolControlView.swift; sourceTree = "<group>"; };
CB1C10DE2F2495ED00B74CD8 /* TrafficBlockingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficBlockingView.swift; sourceTree = "<group>"; };
CB1E97FE2E2262E700A175FD /* NATPassthroughDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NATPassthroughDetailView.swift; sourceTree = "<group>"; };
CB1E98002E226C0F00A175FD /* DirectForwardDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectForwardDetailView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -446,6 +450,8 @@
CB6682BD2E174F9200A27696 /* Instance */ = {
isa = PBXGroup;
children = (
57D9675E2F24B72B00AE3CCB /* NetworkTuningView.swift */,
57D9675F2F24B72B00AE3CCB /* ProtocolControlView.swift */,
57D967502F239AF600AE3CCB /* AddInstanceView.swift */,
57D967512F239AF600AE3CCB /* EditInstanceView.swift */,
57D967522F239AF600AE3CCB /* InstanceFormView.swift */,
Expand Down Expand Up @@ -726,6 +732,8 @@
CB6682A32E172F3C00A27696 /* LoadingStateModifier.swift in Sources */,
CBC6F6012ED815D20024F670 /* ImageAlignment.swift in Sources */,
CB6682D02E1DD9C700A27696 /* UpdateInstanceStatusAction.swift in Sources */,
57D967602F24B72B00AE3CCB /* NetworkTuningView.swift in Sources */,
57D967612F24B72B00AE3CCB /* ProtocolControlView.swift in Sources */,
CB1E98012E226C1E00A175FD /* DirectForwardDetailView.swift in Sources */,
CB6682782E1181CA00A27696 /* Server.swift in Sources */,
CB6682AE2E17345D00A27696 /* AddDirectForwardServiceView.swift in Sources */,
Expand Down
148 changes: 72 additions & 76 deletions NodePass/Server/Instance/InstanceFormView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ struct InstanceFormView: View {
@State private var disableTCP: Bool = false
@State private var disableUDP: Bool = false
@State private var enableProxy: Bool = false

@State private var blockHTTP: Bool = false
@State private var blockTLS: Bool = false
@State private var blockSOCKS: Bool = false

@State private var lbsStrategy: LoadBalancingStrategy = .roundRobin
@State private var urlString: String = ""
@State private var isShowErrorAlert: Bool = false
Expand All @@ -68,6 +66,24 @@ struct InstanceFormView: View {
return blockedTrafficStrings.joined(separator: ", ")
}

private var networkTuningSummary: String {
var settings: [String] = []
if !dnsCache.isEmpty { settings.append("DNS: \(dnsCache)") }
if !dialAddress.isEmpty { settings.append("Dial: \(dialAddress)") }
if !readTimeout.isEmpty { settings.append("Read: \(readTimeout)") }
if !rateLimit.isEmpty { settings.append("Rate: \(rateLimit)") }
if !maxSlots.isEmpty { settings.append("Slot: \(maxSlots)") }
return settings.isEmpty ? "Default" : settings.joined(separator: ", ")
}

private var protocolControlSummary: String {
var settings: [String] = []
if disableTCP { settings.append("TCP Off") }
if disableUDP { settings.append("UDP Off") }
if enableProxy { settings.append("PROXY On") }
return settings.isEmpty ? "Default" : settings.joined(separator: ", ")
}

enum InputMode: String, CaseIterable {
case form = "Form"
case url = "URL"
Expand Down Expand Up @@ -98,10 +114,10 @@ struct InstanceFormView: View {
Text("Input Method")
} footer: {
if inputMode == .form {
Text("Configure instance using form fields")
Text("Configure instance using form fields.")
.foregroundStyle(.secondary)
} else {
Text("Enter instance URL directly")
Text("Enter instance URL directly.")
.foregroundStyle(.secondary)
}
}
Expand Down Expand Up @@ -182,10 +198,10 @@ struct InstanceFormView: View {
Text("Instance Type")
} footer: {
if instanceType == .server {
Text("Listen on tunnel address and forward to/from target")
Text("Listen on tunnel address and forward to/from target.")
.foregroundStyle(.secondary)
} else {
Text("Connect to tunnel address and forward from/to target")
Text("Connect to tunnel address and forward from/to target.")
.foregroundStyle(.secondary)
}
}
Expand All @@ -203,10 +219,10 @@ struct InstanceFormView: View {
} footer: {
VStack(alignment: .leading) {
if instanceType == .server {
Text("Tunnel address to bind, empty IP for all interfaces")
Text("Tunnel address to bind, empty IP for all interfaces.")
.foregroundStyle(.secondary)
} else {
Text("Server address to connect or Client address to bind")
Text("Server address to connect or Client address to bind.")
.foregroundStyle(.secondary)
}
}
Expand Down Expand Up @@ -313,10 +329,10 @@ struct InstanceFormView: View {
} footer: {
VStack(alignment: .leading, spacing: 4) {
if isMultipleTargets {
Text("Configure multiple target addresses for load balancing")
Text("Configure multiple target addresses for load balancing.")
.foregroundStyle(.secondary)
} else {
Text("Single target address to connect or to bind")
Text("Single target address to connect or to bind.")
.foregroundStyle(.secondary)
}
}
Expand Down Expand Up @@ -358,10 +374,10 @@ struct InstanceFormView: View {
} footer: {
VStack(alignment: .leading, spacing: 4) {
if instanceType == .server {
Text("TLS encryption settings")
Text("TLS encryption settings.")
.foregroundStyle(.secondary)
} else {
Text("SNI hostname for TLS connections")
Text("SNI hostname for TLS connections.")
.foregroundStyle(.secondary)
}
}
Expand Down Expand Up @@ -391,64 +407,52 @@ struct InstanceFormView: View {
Text("Connection Pool")
} footer: {
VStack(alignment: .leading, spacing: 4) {
Text("Configure connection pool behavior and limits")
Text("Configure connection pool behavior and limits.")
.foregroundStyle(.secondary)
}
}

Section {
LabeledTextField("DNS Cache Duration", prompt: "5m", text: $dnsCache)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
#endif

LabeledTextField("Dial Address", prompt: "auto", text: $dialAddress)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
#endif

LabeledTextField("Read Timeout", prompt: "0", text: $readTimeout)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
#endif

LabeledTextField("Rate Limit (Mbps)", prompt: "0", text: $rateLimit, isNumberOnly: true)
Picker("Logging Level", selection: $logLevel) {
ForEach(LogLevel.allCases, id: \.self) { level in
Text(level.rawValue).tag(level)
}
}

LabeledTextField("Max Connections", prompt: "65536", text: $maxSlots, isNumberOnly: true)
} header: {
Text("Network Tuning")
} footer: {
VStack(alignment: .leading, spacing: 4) {
Text("DNS: Cache TTL duration in '30s, 5m, 1h'")
.foregroundStyle(.secondary)
Text("Dial: Specific source IP or 'auto' by OS")
.foregroundStyle(.secondary)
Text("Read: Timeout duration or 0 to disable")
.foregroundStyle(.secondary)
Text("Rate: Bandwidth limit or 0 for unlimited")
.foregroundStyle(.secondary)
Text("Slot: Max concurrent connections allowed")
.foregroundStyle(.secondary)
NavigationLink {
NetworkTuningView(
dnsCache: $dnsCache,
dialAddress: $dialAddress,
readTimeout: $readTimeout,
rateLimit: $rateLimit,
maxSlots: $maxSlots
)
} label: {
HStack {
Text("Network Tuning")
Spacer()
Text(networkTuningSummary)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
}

Section {
Toggle("Disable TCP", isOn: $disableTCP)
Toggle("Disable UDP", isOn: $disableUDP)
Toggle("Enable PROXY Protocol", isOn: $enableProxy)
} header: {
Text("Protocol Control")
} footer: {
VStack(alignment: .leading, spacing: 4) {
Text("Control protocol availability and PROXY protocol v1 support")
.foregroundStyle(.secondary)

NavigationLink {
ProtocolControlView(
disableTCP: $disableTCP,
disableUDP: $disableUDP,
enableProxy: $enableProxy
)
} label: {
HStack {
Text("Protocol Control")
Spacer()
Text(protocolControlSummary)
.foregroundStyle(.secondary)
.lineLimit(1)
}
}
}

Section {

NavigationLink {
TrafficBlockingView(blockHTTP: $blockHTTP, blockTLS: $blockTLS, blockSOCKS: $blockSOCKS)
} label: {
Expand All @@ -457,23 +461,14 @@ struct InstanceFormView: View {
Spacer()
Text(blockedTrafficString)
.foregroundStyle(.secondary)
}
}
}

Section {
Picker("Log Level", selection: $logLevel) {
ForEach(LogLevel.allCases, id: \.self) { level in
Text(level.rawValue).tag(level)
.lineLimit(1)
}
}
} header: {
Text("Logging")
Text("Advanced Settings")
} footer: {
VStack(alignment: .leading, spacing: 4) {
Text("Set logging verbosity level")
.foregroundStyle(.secondary)
}
Text("Configure advanced settings and tuning parameters.")
.foregroundStyle(.secondary)
}

Section {
Expand Down Expand Up @@ -517,6 +512,7 @@ struct InstanceFormView: View {
}
.buttonStyle(.borderless)
}
.listRowSeparator(.hidden)
}

Button {
Expand All @@ -533,7 +529,7 @@ struct InstanceFormView: View {
Text("Additional Parameters")
} footer: {
VStack(alignment: .leading, spacing: 4) {
Text("Add custom URL query parameters not covered above")
Text("Add custom URL query parameters not covered above.")
.foregroundStyle(.secondary)
}
}
Expand Down
27 changes: 16 additions & 11 deletions NodePass/Server/Instance/InstanceListView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ struct InstanceListView: View {
@ViewBuilder
private func instanceCard(instance: Instance) -> some View {
InstanceCardView(instance: instance)
.swipeActions(edge: .leading) {
Button {
NPUI.copyToClipboard(instance.url)
} label: {
Label("Copy", systemImage: "doc.on.doc")
}
.tint(.blue)
}
.swipeActions(edge: .trailing) {
Button {
instanceToEdit = instance
} label: {
Label("Edit", systemImage: "pencil")
}
.tint(.orange)
}
.contextMenu {
ControlGroup {
Button {
Expand All @@ -107,17 +123,6 @@ struct InstanceListView: View {
}
}
Divider()
Button {
NPUI.copyToClipboard(instance.url)
} label: {
Label("Copy URL", systemImage: "document.on.document")
}
Button {
instanceToEdit = instance
} label: {
Label("Edit", systemImage: "pencil")
}
Divider()
Button(role: .destructive) {
instanceToDelete = instance
isShowDeleteInstanceAlert = true
Expand Down
61 changes: 61 additions & 0 deletions NodePass/Server/Instance/NetworkTuningView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//
// NetworkTuningView.swift
// NodePass
//
// Created by Yosebyte on 1/24/26.
//

import SwiftUI

struct NetworkTuningView: View {
@Binding var dnsCache: String
@Binding var dialAddress: String
@Binding var readTimeout: String
@Binding var rateLimit: String
@Binding var maxSlots: String

var body: some View {
Form {
Section {
LabeledTextField("DNS Cache Duration", prompt: "5m", text: $dnsCache)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
#endif

LabeledTextField("Dial Address", prompt: "auto", text: $dialAddress)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
#endif

LabeledTextField("Read Timeout", prompt: "0", text: $readTimeout)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
#endif

LabeledTextField("Rate Limit (Mbps)", prompt: "0", text: $rateLimit, isNumberOnly: true)

LabeledTextField("Max Connections", prompt: "65536", text: $maxSlots, isNumberOnly: true)
} footer: {
VStack(alignment: .leading, spacing: 4) {
Text("DNS: Cache TTL duration in '30s, 5m, 1h'.")
.foregroundStyle(.secondary)
Text("Dial: Specific source IP or 'auto' by OS.")
.foregroundStyle(.secondary)
Text("Read: Timeout duration or 0 to disable.")
.foregroundStyle(.secondary)
Text("Rate: Bandwidth limit or 0 for unlimited.")
.foregroundStyle(.secondary)
Text("Slot: Max concurrent connections allowed.")
.foregroundStyle(.secondary)
}
}
}
.navigationTitle("Network Tuning")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
}
}
Loading