Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit3470cf0

Browse files
committed
feat: pass agent updates to UI
1 parent15f2bcc commit3470cf0

20 files changed

+390
-294
lines changed

‎Coder Desktop/Coder Desktop/Preview Content/PreviewVPN.swift

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ import SwiftUI
44
@MainActor
55
finalclass PreviewVPN:Coder_Desktop.VPNService{
66
@Publishedvarstate:Coder_Desktop.VPNServiceState=.disabled
7-
@Publishedvaragents:[Coder_Desktop.Agent]=[
8-
Agent(id:UUID(),name:"dogfood2",status:.error, copyableDNS:"asdf.coder",workspaceName:"dogfood2"),
9-
Agent(id:UUID(),name:"testing-a-very-long-name",status:.okay, copyableDNS:"asdf.coder",
10-
workspaceName:"testing-a-very-long-name"),
11-
Agent(id:UUID(),name:"opensrc",status:.warn, copyableDNS:"asdf.coder",workspaceName:"opensrc"),
12-
Agent(id:UUID(),name:"gvisor",status:.off, copyableDNS:"asdf.coder",workspaceName:"gvisor"),
13-
Agent(id:UUID(),name:"example",status:.off, copyableDNS:"asdf.coder",workspaceName:"example"),
14-
Agent(id:UUID(),name:"dogfood2",status:.error, copyableDNS:"asdf.coder",workspaceName:"dogfood2"),
15-
Agent(id:UUID(),name:"testing-a-very-long-name",status:.okay, copyableDNS:"asdf.coder",
16-
workspaceName:"testing-a-very-long-name"),
17-
Agent(id:UUID(),name:"opensrc",status:.warn, copyableDNS:"asdf.coder",workspaceName:"opensrc"),
18-
Agent(id:UUID(),name:"gvisor",status:.off, copyableDNS:"asdf.coder",workspaceName:"gvisor"),
19-
Agent(id:UUID(),name:"example",status:.off, copyableDNS:"asdf.coder",workspaceName:"example"),
7+
@Publishedvaragents:[UUID:Coder_Desktop.Agent]=[
8+
UUID():Agent(id:UUID(), status:.error, copyableDNS:"asdf.coder",wsName:"dogfood2", wsID:UUID()),
9+
UUID():Agent(id:UUID(), status:.okay, copyableDNS:"asdf.coder", wsName:"testing-a-very-long-name",
10+
wsID:UUID()),
11+
UUID():Agent(id:UUID(), status:.warn, copyableDNS:"asdf.coder",wsName:"opensrc", wsID:UUID()),
12+
UUID():Agent(id:UUID(), status:.off, copyableDNS:"asdf.coder",wsName:"gvisor", wsID:UUID()),
13+
UUID():Agent(id:UUID(), status:.off, copyableDNS:"asdf.coder",wsName:"example", wsID:UUID()),
14+
UUID():Agent(id:UUID(), status:.error, copyableDNS:"asdf.coder",wsName:"dogfood2", wsID:UUID()),
15+
UUID():Agent(id:UUID(), status:.okay, copyableDNS:"asdf.coder", wsName:"testing-a-very-long-name",
16+
wsID:UUID()),
17+
UUID():Agent(id:UUID(), status:.warn, copyableDNS:"asdf.coder",wsName:"opensrc", wsID:UUID()),
18+
UUID():Agent(id:UUID(), status:.off, copyableDNS:"asdf.coder",wsName:"gvisor", wsID:UUID()),
19+
UUID():Agent(id:UUID(), status:.off, copyableDNS:"asdf.coder",wsName:"example", wsID:UUID()),
2020
]
2121
letshouldFail:Bool
22+
letlongError="This is a long error to test the UI with long error messages"
2223

2324
init(shouldFail:Bool=false){
2425
self.shouldFail= shouldFail
@@ -35,10 +36,10 @@ final class PreviewVPN: Coder_Desktop.VPNService {
3536
do{
3637
tryawaitTask.sleep(for:.seconds(5))
3738
}catch{
38-
state=.failed(.longTestError)
39+
state=.failed(.internalError(longError))
3940
return
4041
}
41-
state= shouldFail?.failed(.longTestError):.connected
42+
state= shouldFail?.failed(.internalError(longError)):.connected
4243
}
4344
defer{ startTask=nil}
4445
await startTask?.value
@@ -57,7 +58,7 @@ final class PreviewVPN: Coder_Desktop.VPNService {
5758
do{
5859
tryawaitTask.sleep(for:.seconds(5))
5960
}catch{
60-
state=.failed(.longTestError)
61+
state=.failed(.internalError(longError))
6162
return
6263
}
6364
state=.disabled

‎Coder Desktop/Coder Desktop/VPNService.swift

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ import NetworkExtension
22
import os
33
import SwiftUI
44
import VPNLib
5-
import VPNXPC
65

76
@MainActor
87
protocolVPNService:ObservableObject{
98
varstate:VPNServiceState{get}
10-
varagents:[Agent]{get}
9+
varagents:[UUID:Agent]{get}
1110
func start()async
12-
// Stop must be idempotent
1311
func stop()async
1412
func configureTunnelProviderProtocol(proto:NETunnelProviderProtocol?)
1513
}
@@ -26,12 +24,9 @@ enum VPNServiceError: Error, Equatable {
2624
case internalError(String)
2725
case systemExtensionError(SystemExtensionState)
2826
case networkExtensionError(NetworkExtensionState)
29-
case longTestError
3027

3128
vardescription:String{
3229
switchself{
33-
case.longTestError:
34-
"This is a long error to test the UI with long errors"
3530
caselet.internalError(description):
3631
"Internal Error:\(description)"
3732
caselet.systemExtensionError(state):
@@ -47,6 +42,7 @@ final class CoderVPNService: NSObject, VPNService {
4742
varlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"vpn")
4843
lazyvarxpc:VPNXPCInterface=.init(vpn:self)
4944
varterminating=false
45+
varworkspaces:[UUID:String]=[:]
5046

5147
@PublishedvartunnelState:VPNServiceState=.disabled
5248
@PublishedvarsysExtnState:SystemExtensionState=.uninstalled
@@ -61,7 +57,7 @@ final class CoderVPNService: NSObject, VPNService {
6157
return tunnelState
6258
}
6359

64-
@Publishedvaragents:[Agent]=[]
60+
@Publishedvaragents:[UUID:Agent]=[:]
6561

6662
// systemExtnDelegate holds a reference to the SystemExtensionDelegate so that it doesn't get
6763
// garbage collected while the OSSystemExtensionRequest is in flight, since the OS framework
@@ -74,6 +70,16 @@ final class CoderVPNService: NSObject, VPNService {
7470
Task{
7571
awaitloadNetworkExtension()
7672
}
73+
NotificationCenter.default.addObserver(
74+
self,
75+
selector: #selector(vpnDidUpdate(_:)),
76+
name:.NEVPNStatusDidChange,
77+
object:nil
78+
)
79+
}
80+
81+
deinit{
82+
NotificationCenter.default.removeObserver(self)
7783
}
7884

7985
func start()async{
@@ -84,16 +90,14 @@ final class CoderVPNService: NSObject, VPNService {
8490
return
8591
}
8692

93+
awaitenableNetworkExtension()
8794
// this ping is somewhat load bearing since it causes xpc to init
8895
xpc.ping()
89-
tunnelState=.connecting
90-
awaitenableNetworkExtension()
9196
logger.debug("network extension enabled")
9297
}
9398

9499
func stop()async{
95100
guard tunnelState==.connectedelse{return}
96-
tunnelState=.disconnecting
97101
awaitdisableNetworkExtension()
98102
logger.info("network extension stopped")
99103
}
@@ -131,31 +135,88 @@ final class CoderVPNService: NSObject, VPNService {
131135
}
132136

133137
func onExtensionPeerUpdate(_ data:Data){
134-
// TODO: handle peer update
135138
logger.info("network extension peer update")
136139
do{
137-
letmsg=tryVpn_TunnelMessage(serializedBytes: data)
140+
letmsg=tryVpn_PeerUpdate(serializedBytes: data)
138141
debugPrint(msg)
142+
applyPeerUpdate(with: msg)
139143
}catch{
140144
logger.error("failed to decode peer update\(error)")
141145
}
142146
}
143147

144-
func onExtensionStart(){
145-
logger.info("network extension reported started")
146-
tunnelState=.connected
147-
}
148+
func applyPeerUpdate(with update:Vpn_PeerUpdate){
149+
// Delete agents
150+
letdeletedWorkspaceIDs=Set(update.deletedWorkspaces.compactMap{UUID(uuidData: $0.id)})
151+
letdeletedAgentIDs=Set(update.deletedAgents.compactMap{UUID(uuidData: $0.id)})
152+
foragentIDin deletedAgentIDs{
153+
agents[agentID]=nil
154+
}
155+
forworkspaceIDin deletedWorkspaceIDs{
156+
workspaces[workspaceID]=nil
157+
for(id, agent)in agentswhere agent.wsID== workspaceID{
158+
agents[id]=nil
159+
}
160+
}
148161

149-
func onExtensionStop(){
150-
logger.info("network extension reported stopped")
151-
tunnelState=.disabled
152-
if terminating{
153-
NSApp.reply(toApplicationShouldTerminate:true)
162+
// Update workspaces
163+
forworkspaceProtoin update.upsertedWorkspaces{
164+
iflet workspaceID=UUID(uuidData: workspaceProto.id){
165+
workspaces[workspaceID]= workspaceProto.name
166+
}
167+
}
168+
169+
foragentProtoin update.upsertedAgents{
170+
guardlet agentID=UUID(uuidData: agentProto.id)else{
171+
continue
172+
}
173+
guardlet workspaceID=UUID(uuidData: agentProto.workspaceID)else{
174+
continue
175+
}
176+
letworkspaceName=workspaces[workspaceID]??"Unknown Workspace"
177+
letnewAgent=Agent(
178+
id: agentID,
179+
// If last handshake was not within last five minutes, the agent is unhealthy
180+
status: agentProto.lastHandshake.date>Date.now.addingTimeInterval(-300)?.okay:.off,
181+
copyableDNS: agentProto.fqdn.first??"UNKNOWN",
182+
wsName: workspaceName,
183+
wsID: workspaceID
184+
)
185+
186+
agents[agentID]= newAgent
154187
}
155188
}
189+
}
156190

157-
func onExtensionError(_ error:NSError){
158-
logger.error("network extension reported error:\(error)")
159-
tunnelState=.failed(.internalError(error.localizedDescription))
191+
extensionCoderVPNService{
192+
@objcprivatefunc vpnDidUpdate(_ notification:Notification){
193+
guardlet connection= notification.objectas?NETunnelProviderSessionelse{
194+
return
195+
}
196+
switch connection.status{
197+
case.disconnected:
198+
if terminating{
199+
NSApp.reply(toApplicationShouldTerminate:true)
200+
}
201+
connection.fetchLastDisconnectError{ errin
202+
self.tunnelState=iflet err{
203+
.failed(.internalError(err.localizedDescription))
204+
}else{
205+
.disabled
206+
}
207+
}
208+
case.connecting:
209+
tunnelState=.connecting
210+
case.connected:
211+
tunnelState=.connected
212+
case.reasserting:
213+
tunnelState=.connecting
214+
case.disconnecting:
215+
tunnelState=.disconnecting
216+
case.invalid:
217+
tunnelState=.failed(.networkExtensionError(.unconfigured))
218+
@unknowndefault:
219+
tunnelState=.disabled
220+
}
160221
}
161222
}

‎Coder Desktop/Coder Desktop/Views/Agent.swift

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
import SwiftUI
22

3-
structAgent:Identifiable,Equatable{
3+
structAgent:Identifiable,Equatable,Comparable{
44
letid:UUID
5-
letname:String
65
letstatus:AgentStatus
76
letcopyableDNS:String
8-
letworkspaceName:String
7+
letwsName:String
8+
letwsID:UUID
9+
10+
// Agents are sorted by status, and then by name
11+
staticfunc<(lhs:Agent, rhs:Agent)->Bool{
12+
if lhs.status!= rhs.status{
13+
return lhs.status< rhs.status
14+
}
15+
return lhs.wsName.localizedCompare(rhs.wsName)==.orderedAscending
16+
}
917
}
1018

11-
enumAgentStatus:Equatable{
12-
case okay
13-
case warn
14-
case error
15-
case off
19+
enumAgentStatus:Int,Equatable,Comparable{
20+
case okay=0
21+
case warn=1
22+
case error=2
23+
case off=3
1624

1725
publicvarcolor:Color{
1826
switchself{
@@ -22,16 +30,20 @@ enum AgentStatus: Equatable {
2230
case.off:.gray
2331
}
2432
}
33+
34+
staticfunc<(lhs:AgentStatus, rhs:AgentStatus)->Bool{
35+
lhs.rawValue< rhs.rawValue
36+
}
2537
}
2638

2739
structAgentRowView:View{
28-
letworkspace:Agent
40+
letagent:Agent
2941
letbaseAccessURL:URL
3042
@StateprivatevarnameIsSelected:Bool=false
3143
@StateprivatevarcopyIsSelected:Bool=false
3244

3345
privatevarfmtWsName:AttributedString{
34-
varformattedName=AttributedString(workspace.name)
46+
varformattedName=AttributedString(agent.wsName)
3547
formattedName.foregroundColor=.primary
3648
varcoderPart=AttributedString(".coder")
3749
coderPart.foregroundColor=.gray
@@ -41,7 +53,7 @@ struct AgentRowView: View {
4153

4254
privatevarwsURL:URL{
4355
// TODO: CoderVPN currently only supports owned workspaces
44-
baseAccessURL.appending(path:"@me").appending(path:workspace.workspaceName)
56+
baseAccessURL.appending(path:"@me").appending(path:agent.wsName)
4557
}
4658

4759
varbody:someView{
@@ -50,10 +62,10 @@ struct AgentRowView: View {
5062
HStack(spacing:Theme.Size.trayPadding){
5163
ZStack{
5264
Circle()
53-
.fill(workspace.status.color.opacity(0.4))
65+
.fill(agent.status.color.opacity(0.4))
5466
.frame(width:12, height:12)
5567
Circle()
56-
.fill(workspace.status.color.opacity(1.0))
68+
.fill(agent.status.color.opacity(1.0))
5769
.frame(width:7, height:7)
5870
}
5971
Text(fmtWsName).lineLimit(1).truncationMode(.tail)
@@ -69,7 +81,7 @@ struct AgentRowView: View {
6981
}.buttonStyle(.plain)
7082
Button{
7183
// TODO: Proper clipboard abstraction
72-
NSPasteboard.general.setString(workspace.copyableDNS, forType:.string)
84+
NSPasteboard.general.setString(agent.copyableDNS, forType:.string)
7385
} label:{
7486
Image(systemName:"doc.on.doc")
7587
.symbolVariant(.fill)

‎Coder Desktop/Coder Desktop/Views/Agents.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ struct Agents<VPN: VPNService, S: Session>: View {
1010

1111
varbody:someView{
1212
Group{
13-
//Workspaces List
13+
//Agents List
1414
if vpn.state==.connected{
15-
letvisibleData= viewAll? vpn.agents:Array(vpn.agents.prefix(defaultVisibleRows))
16-
ForEach(visibleData, id: \.id){ workspacein
17-
AgentRowView(workspace: workspace, baseAccessURL: session.baseAccessURL!)
15+
letsortedAgents= vpn.agents.values.sorted()
16+
letvisibleData= viewAll?sortedAgents[...]: sortedAgents.prefix(defaultVisibleRows)
17+
ForEach(visibleData, id: \.id){ agentin
18+
AgentRowView(agent: agent, baseAccessURL: session.baseAccessURL!)
1819
.padding(.horizontal,Theme.Size.trayMargin)
1920
}
2021
if vpn.agents.count> defaultVisibleRows{

‎Coder Desktop/Coder Desktop/Views/Util.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,16 @@ final class Inspection<V> {
1212
}
1313
}
1414
}
15+
16+
extensionUUID{
17+
init?(uuidData:Data){
18+
guard uuidData.count==16else{
19+
returnnil
20+
}
21+
varuuid:uuid_t=(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
22+
withUnsafeMutableBytes(of:&uuid){
23+
$0.copyBytes(from: uuidData)
24+
}
25+
self.init(uuid: uuid)
26+
}
27+
}

‎Coder Desktop/Coder Desktop/XPCInterface.swift

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Foundation
2+
import NetworkExtension
23
import os
3-
importVPNXPC
4+
importVPNLib
45

56
@objcfinalclassVPNXPCInterface:NSObject,VPNXPCClientCallbackProtocol,@uncheckedSendable{
67
privatevarsvc:CoderVPNService
@@ -49,22 +50,4 @@ import VPNXPC
4950
svc.onExtensionPeerUpdate(data)
5051
}
5152
}
52-
53-
func onStart(){
54-
Task{@MainActorin
55-
svc.onExtensionStart()
56-
}
57-
}
58-
59-
func onStop(){
60-
Task{@MainActorin
61-
svc.onExtensionStop()
62-
}
63-
}
64-
65-
func onError(_ err:NSError){
66-
Task{@MainActorin
67-
svc.onExtensionError(err)
68-
}
69-
}
7053
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp