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

Commitcaf84f1

Browse files
committed
fix: display offline workspaces
1 parent024d7e3 commitcaf84f1

File tree

11 files changed

+404
-287
lines changed

11 files changed

+404
-287
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import SwiftUI
44
@MainActor
55
finalclass PreviewVPN:Coder_Desktop.VPNService{
66
@Publishedvarstate:Coder_Desktop.VPNServiceState=.disabled
7-
@Publishedvaragents:[UUID:Coder_Desktop.Agent]=[
7+
@PublishedvarmenuState:VPNMenuState=.init(agents:[
88
UUID():Agent(id:UUID(), name:"dev", status:.error, copyableDNS:"asdf.coder", wsName:"dogfood2",
99
wsID:UUID()),
1010
UUID():Agent(id:UUID(), name:"dev", status:.okay, copyableDNS:"asdf.coder",
@@ -25,7 +25,7 @@ final class PreviewVPN: Coder_Desktop.VPNService {
2525
wsID:UUID()),
2626
UUID():Agent(id:UUID(), name:"dev", status:.off, copyableDNS:"asdf.coder", wsName:"example",
2727
wsID:UUID()),
28-
]
28+
], workspaces:[:])
2929
letshouldFail:Bool
3030
letlongError="This is a long error to test the UI with long error messages"
3131

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import Foundation
2+
import SwiftUI
3+
import VPNLib
4+
5+
structAgent:Identifiable,Equatable,Comparable{
6+
letid:UUID
7+
letname:String
8+
letstatus:AgentStatus
9+
letcopyableDNS:String
10+
letwsName:String
11+
letwsID:UUID
12+
13+
// Agents are sorted by status, and then by name
14+
staticfunc<(lhs:Agent, rhs:Agent)->Bool{
15+
if lhs.status!= rhs.status{
16+
return lhs.status< rhs.status
17+
}
18+
return lhs.wsName.localizedCompare(rhs.wsName)==.orderedAscending
19+
}
20+
}
21+
22+
enumAgentStatus:Int,Equatable,Comparable{
23+
case okay=0
24+
case warn=1
25+
case error=2
26+
case off=3
27+
28+
publicvarcolor:Color{
29+
switchself{
30+
case.okay:.green
31+
case.warn:.yellow
32+
case.error:.red
33+
case.off:.gray
34+
}
35+
}
36+
37+
staticfunc<(lhs:AgentStatus, rhs:AgentStatus)->Bool{
38+
lhs.rawValue< rhs.rawValue
39+
}
40+
}
41+
42+
structWorkspace:Identifiable,Equatable,Comparable{
43+
letid:UUID
44+
letname:String
45+
varagents:[UUID]
46+
47+
staticfunc<(lhs:Workspace, rhs:Workspace)->Bool{
48+
lhs.name.localizedCompare(rhs.name)==.orderedAscending
49+
}
50+
}
51+
52+
structVPNMenuState{
53+
varagents:[UUID:Agent]=[:]
54+
varworkspaces:[UUID:Workspace]=[:]
55+
56+
mutatingfunc upsertAgent(_ agent:Vpn_Agent){
57+
guardlet id=UUID(uuidData: agent.id)else{return}
58+
guardlet wsID=UUID(uuidData: agent.workspaceID)else{return}
59+
// An existing agent with the same name, belonging to the same workspace
60+
// is from a previous workspace build, and should be removed.
61+
agents.filter{ $0.value.name== agent.name && $0.value.wsID== wsID}
62+
.forEach{agents[$0.key]=nil}
63+
workspaces[wsID]?.agents.append(id)
64+
letwsName=workspaces[wsID]?.name??"Unknown Workspace"
65+
agents[id]=Agent(
66+
id: id,
67+
name: agent.name,
68+
// If last handshake was not within last five minutes, the agent is unhealthy
69+
status: agent.lastHandshake.date>Date.now.addingTimeInterval(-300)?.okay:.warn,
70+
// Choose the shortest hostname, and remove trailing dot if present
71+
copyableDNS: agent.fqdn.min(by:{ $0.count< $1.count})
72+
.map{ $0.hasSuffix(".")?String($0.dropLast()): $0}??"UNKNOWN",
73+
wsName: wsName,
74+
wsID: wsID
75+
)
76+
}
77+
78+
mutatingfunc deleteAgent(withId id:Data){
79+
guardlet id=UUID(uuidData: id)else{return}
80+
// Update Workspaces
81+
iflet agent=agents[id],var ws=workspaces[agent.wsID]{
82+
ws.agents.removeAll{ $0== id}
83+
workspaces[agent.wsID]= ws
84+
}
85+
agents[id]=nil
86+
}
87+
88+
mutatingfunc upsertWorkspace(_ workspace:Vpn_Workspace){
89+
guardlet id=UUID(uuidData: workspace.id)else{return}
90+
workspaces[id]=Workspace(id: id, name: workspace.name, agents:[])
91+
}
92+
93+
mutatingfunc deleteWorkspace(withId id:Data){
94+
guardlet wsID=UUID(uuidData: id)else{return}
95+
agents.filter{ _, valuein
96+
value.wsID== wsID
97+
}.forEach{ key, _in
98+
agents[key]=nil
99+
}
100+
workspaces[wsID]=nil
101+
}
102+
103+
func sorted()->[VPNMenuItem]{
104+
varitems= agents.values.map{VPNMenuItem.agent($0)}
105+
// Workspaces with no agents are shown as offline
106+
items+= workspaces.filter{ _, valuein
107+
value.agents.isEmpty
108+
}.map{VPNMenuItem.offlineWorkspace(Workspace(id: $0.key, name: $0.value.name, agents: $0.value.agents))}
109+
return items.sorted()
110+
}
111+
112+
mutatingfunc clear(){
113+
agents.removeAll()
114+
workspaces.removeAll()
115+
}
116+
}

‎Coder Desktop/Coder Desktop/VPNService.swift

Lines changed: 8 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import VPNLib
66
@MainActor
77
protocolVPNService:ObservableObject{
88
varstate:VPNServiceState{get}
9-
varagents:[UUID:Agent]{get}
9+
varmenuState:VPNMenuState{get}
1010
func start()async
1111
func stop()async
1212
func configureTunnelProviderProtocol(proto:NETunnelProviderProtocol?)
@@ -41,7 +41,6 @@ enum VPNServiceError: Error, Equatable {
4141
finalclassCoderVPNService:NSObject,VPNService{
4242
varlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"vpn")
4343
lazyvarxpc:VPNXPCInterface=.init(vpn:self)
44-
varworkspaces:[UUID:String]=[:]
4544

4645
@PublishedvartunnelState:VPNServiceState=.disabled
4746
@PublishedvarsysExtnState:SystemExtensionState=.uninstalled
@@ -56,7 +55,7 @@ final class CoderVPNService: NSObject, VPNService {
5655
return tunnelState
5756
}
5857

59-
@Publishedvaragents:[UUID:Agent]=[:]
58+
@PublishedvarmenuState:VPNMenuState=.init()
6059

6160
// systemExtnDelegate holds a reference to the SystemExtensionDelegate so that it doesn't get
6261
// garbage collected while the OSSystemExtensionRequest is in flight, since the OS framework
@@ -85,11 +84,6 @@ final class CoderVPNService: NSObject, VPNService {
8584
NotificationCenter.default.removeObserver(self)
8685
}
8786

88-
func clearPeers(){
89-
agents=[:]
90-
workspaces=[:]
91-
}
92-
9387
func start()async{
9488
switch tunnelState{
9589
case.disabled,.failed:
@@ -150,7 +144,7 @@ final class CoderVPNService: NSObject, VPNService {
150144
do{
151145
letmsg=tryVpn_PeerUpdate(serializedBytes: data)
152146
debugPrint(msg)
153-
clearPeers()
147+
menuState.clear()
154148
applyPeerUpdate(with: msg)
155149
}catch{
156150
logger.error("failed to decode peer update\(error)")
@@ -159,53 +153,11 @@ final class CoderVPNService: NSObject, VPNService {
159153

160154
func applyPeerUpdate(with update:Vpn_PeerUpdate){
161155
// Delete agents
162-
update.deletedAgents
163-
.compactMap{UUID(uuidData: $0.id)}
164-
.forEach{ agentIDin
165-
agents[agentID]=nil
166-
}
167-
update.deletedWorkspaces
168-
.compactMap{UUID(uuidData: $0.id)}
169-
.forEach{ workspaceIDin
170-
workspaces[workspaceID]=nil
171-
for(id, agent)in agentswhere agent.wsID== workspaceID{
172-
agents[id]=nil
173-
}
174-
}
175-
176-
// Update workspaces
177-
forworkspaceProtoin update.upsertedWorkspaces{
178-
iflet workspaceID=UUID(uuidData: workspaceProto.id){
179-
workspaces[workspaceID]= workspaceProto.name
180-
}
181-
}
182-
183-
foragentProtoin update.upsertedAgents{
184-
guardlet agentID=UUID(uuidData: agentProto.id)else{
185-
continue
186-
}
187-
guardlet workspaceID=UUID(uuidData: agentProto.workspaceID)else{
188-
continue
189-
}
190-
letworkspaceName=workspaces[workspaceID]??"Unknown Workspace"
191-
letnewAgent=Agent(
192-
id: agentID,
193-
name: agentProto.name,
194-
// If last handshake was not within last five minutes, the agent is unhealthy
195-
status: agentProto.lastHandshake.date>Date.now.addingTimeInterval(-300)?.okay:.off,
196-
copyableDNS: agentProto.fqdn.first??"UNKNOWN",
197-
wsName: workspaceName,
198-
wsID: workspaceID
199-
)
200-
201-
// An existing agent with the same name, belonging to the same workspace
202-
// is from a previous workspace build, and should be removed.
203-
agents
204-
.filter{ $0.value.name== agentProto.name && $0.value.wsID== workspaceID}
205-
.forEach{agents[$0.key]=nil}
206-
207-
agents[agentID]= newAgent
208-
}
156+
update.deletedAgents.forEach{ menuState.deleteAgent(withId: $0.id)}
157+
update.deletedWorkspaces.forEach{ menuState.deleteWorkspace(withId: $0.id)}
158+
// Upsert workspaces before agents to populate agent workspace names
159+
update.upsertedWorkspaces.forEach{ menuState.upsertWorkspace($0)}
160+
update.upsertedAgents.forEach{ menuState.upsertAgent($0)}
209161
}
210162
}
211163

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

Lines changed: 0 additions & 99 deletions
This file was deleted.

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ struct Agents<VPN: VPNService, S: Session>: View {
1212
Group{
1313
// Agents List
1414
if vpn.state==.connected{
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!)
15+
letitems= vpn.menuState.sorted()
16+
letvisibleItems= viewAll?items[...]:items.prefix(defaultVisibleRows)
17+
ForEach(visibleItems, id: \.id){ agentin
18+
MenuItemView(item: agent, baseAccessURL: session.baseAccessURL!)
1919
.padding(.horizontal,Theme.Size.trayMargin)
2020
}
21-
ifvpn.agents.count> defaultVisibleRows{
21+
ifitems.count> defaultVisibleRows{
2222
Toggle(isOn: $viewAll){
23-
Text(viewAll?"ShowLess":"ShowAll")
23+
Text(viewAll?"Showless":"Showall")
2424
.font(.headline)
2525
.foregroundColor(.gray)
2626
.padding(.horizontal,Theme.Size.trayInset)

‎Coder Desktop/Coder Desktop/Views/VPNMenu.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct VPNMenu<VPN: VPNService, S: Session>: View {
2828
.disabled(vpnDisabled)
2929
}
3030
Divider()
31-
Text("Workspace Agents")
31+
Text("Workspaces")
3232
.font(.headline)
3333
.foregroundColor(.gray)
3434
VPNState<VPN,S>()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp