Skip to content
Open
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
7 changes: 7 additions & 0 deletions NodePass/Server/Instance/InstanceCardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ struct InstanceCardView: View {
Badge("\(ping) ms", backgroundColor: .blue, textColor: .white)
}
Spacer()
if let alias = instance.alias, !alias.isEmpty {
Text(alias)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(.tail)
}
}

Text(instance.url)
Expand Down
171 changes: 85 additions & 86 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,16 +198,16 @@ 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)
}
}

Section {
LabeledTextField("IP", prompt: instanceType == .server ? "" : "", text: $tunnelAddress)
LabeledTextField("IP", prompt: "Optional", text: $tunnelAddress)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
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 @@ -300,7 +316,7 @@ struct InstanceFormView: View {
#endif
}
} else {
LabeledTextField("IP", prompt: instanceType == .server ? "" : "", text: $targetAddress)
LabeledTextField("IP", prompt: "Optional", text: $targetAddress)
.autocorrectionDisabled()
#if os(iOS)
.textInputAutocapitalization(.never)
Expand All @@ -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 Expand Up @@ -792,7 +788,7 @@ struct InstanceFormView: View {
queryParams.append("lbs=\(lbsStrategy.rawValue)")
}

if isAdvancedModeEnabled && logLevel != .info {
if logLevel != .info {
queryParams.append("log=\(logLevel.rawValue)")
}

Expand Down Expand Up @@ -823,17 +819,20 @@ struct InstanceFormView: View {
let instanceService = InstanceService()
do {
if let instance = instance {
_ = try await instanceService.updateInstance(
baseURLString: server.url,
apiKey: server.key,
id: instance.id,
url: url
)
if instance.url != url {
_ = try await instanceService.updateInstance(
baseURLString: server.url,
apiKey: server.key,
id: instance.id,
url: url
)
}
} else {
_ = try await instanceService.createInstance(
baseURLString: server.url,
apiKey: server.key,
url: url
url: url,
alias: String(localized: "Untitled")
)
}
await MainActor.run {
Expand Down
Loading