Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 6462177

Browse files
committedApr 10, 2025·
feat: use the deployment's hostname suffix in the UI
1 parent 918bacd commit 6462177

File tree

4 files changed

+67
-8
lines changed

4 files changed

+67
-8
lines changed
 

‎Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,12 @@ class AppDelegate: NSObject, NSApplicationDelegate {
6565
title: "Coder Desktop",
6666
image: "MenuBarIcon",
6767
onAppear: {
68-
// If the VPN is enabled, it's likely the token isn't expired
68+
// If the VPN is enabled, it's likely the token hasn't expired,
69+
// and the deployment config is up to date.
6970
guard case .disabled = self.vpn.state, self.state.hasSession else { return }
7071
Task { @MainActor in
7172
await self.state.handleTokenExpiry()
73+
await self.state.refreshDeploymentConfig()
7274
}
7375
}, content: {
7476
VPNMenu<CoderVPNService, MutagenDaemon>().frame(width: 256)

‎Coder-Desktop/Coder-Desktop/State.swift

+43-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ class AppState: ObservableObject {
2525
}
2626
}
2727

28+
@Published private(set) var hostnameSuffix: String {
29+
didSet {
30+
guard persistent else { return }
31+
UserDefaults.standard.set(hostnameSuffix, forKey: Keys.hostnameSuffix)
32+
}
33+
}
34+
35+
static let defaultHostnameSuffix: String = "coder"
36+
2837
// Stored in Keychain
2938
@Published private(set) var sessionToken: String? {
3039
didSet {
@@ -33,6 +42,8 @@ class AppState: ObservableObject {
3342
}
3443
}
3544

45+
var client: Client?
46+
3647
@Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) {
3748
didSet {
3849
reconfigure()
@@ -80,7 +91,7 @@ class AppState: ObservableObject {
8091
private let keychain: Keychain
8192
private let persistent: Bool
8293

83-
let onChange: ((NETunnelProviderProtocol?) -> Void)?
94+
private let onChange: ((NETunnelProviderProtocol?) -> Void)?
8495

8596
// reconfigure must be called when any property used to configure the VPN changes
8697
public func reconfigure() {
@@ -94,6 +105,10 @@ class AppState: ObservableObject {
94105
self.onChange = onChange
95106
keychain = Keychain(service: Bundle.main.bundleIdentifier!)
96107
_hasSession = Published(initialValue: persistent ? UserDefaults.standard.bool(forKey: Keys.hasSession) : false)
108+
_hostnameSuffix = Published(
109+
initialValue: persistent ? UserDefaults.standard
110+
.string(forKey: Keys.hostnameSuffix) ?? Self.defaultHostnameSuffix : Self.defaultHostnameSuffix
111+
)
97112
_baseAccessURL = Published(
98113
initialValue: persistent ? UserDefaults.standard.url(forKey: Keys.baseAccessURL) : nil
99114
)
@@ -107,21 +122,30 @@ class AppState: ObservableObject {
107122
if sessionToken == nil || sessionToken!.isEmpty == true {
108123
clearSession()
109124
}
125+
client = Client(
126+
url: baseAccessURL!,
127+
token: sessionToken!,
128+
headers: useLiteralHeaders ? literalHeaders.map { $0.toSDKHeader() } : []
129+
)
110130
}
111131
}
112132

113133
public func login(baseAccessURL: URL, sessionToken: String) {
114134
hasSession = true
115135
self.baseAccessURL = baseAccessURL
116136
self.sessionToken = sessionToken
137+
client = Client(
138+
url: baseAccessURL,
139+
token: sessionToken,
140+
headers: useLiteralHeaders ? literalHeaders.map { $0.toSDKHeader() } : []
141+
)
117142
reconfigure()
118143
}
119144

120145
public func handleTokenExpiry() async {
121146
if hasSession {
122-
let client = Client(url: baseAccessURL!, token: sessionToken!)
123147
do {
124-
_ = try await client.user("me")
148+
_ = try await client!.user("me")
125149
} catch let SDKError.api(apiErr) {
126150
// Expired token
127151
if apiErr.statusCode == 401 {
@@ -135,9 +159,24 @@ class AppState: ObservableObject {
135159
}
136160
}
137161

162+
public func refreshDeploymentConfig() async {
163+
if hasSession {
164+
do {
165+
let config = try await client!.sshConfiguration()
166+
hostnameSuffix = config.hostname_suffix ?? Self.defaultHostnameSuffix
167+
} catch {
168+
// If fetching the config fails, there's likely a bigger issue.
169+
// We'll show an error in the UI if they try and do something
170+
logger.error("failed to refresh deployment config: \(error)")
171+
return
172+
}
173+
}
174+
}
175+
138176
public func clearSession() {
139177
hasSession = false
140178
sessionToken = nil
179+
client = nil
141180
reconfigure()
142181
}
143182

@@ -159,6 +198,7 @@ class AppState: ObservableObject {
159198
static let hasSession = "hasSession"
160199
static let baseAccessURL = "baseAccessURL"
161200
static let sessionToken = "sessionToken"
201+
static let hostnameSuffix = "hostnameSuffix"
162202

163203
static let useLiteralHeaders = "UseLiteralHeaders"
164204
static let literalHeaders = "LiteralHeaders"

‎Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift

+7-4
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,24 @@ enum VPNMenuItem: Equatable, Comparable, Identifiable {
4242
}
4343

4444
struct MenuItemView: View {
45+
@EnvironmentObject var state: AppState
46+
4547
let item: VPNMenuItem
4648
let baseAccessURL: URL
4749
@State private var nameIsSelected: Bool = false
4850
@State private var copyIsSelected: Bool = false
4951

5052
private var itemName: AttributedString {
5153
let name = switch item {
52-
case let .agent(agent): agent.primaryHost ?? "\(item.wsName).coder"
53-
case .offlineWorkspace: "\(item.wsName).coder"
54+
case let .agent(agent): agent.primaryHost ?? "\(item.wsName).\(state.hostnameSuffix)"
55+
case .offlineWorkspace: "\(item.wsName).\(state.hostnameSuffix)"
5456
}
5557

5658
var formattedName = AttributedString(name)
5759
formattedName.foregroundColor = .primary
58-
if let range = formattedName.range(of: ".coder") {
59-
formattedName[range].foregroundColor = .secondary
60+
61+
if let lastDot = formattedName.range(of: ".", options: .backwards) {
62+
formattedName[lastDot.lowerBound ..< formattedName.endIndex].foregroundColor = .secondary
6063
}
6164
return formattedName
6265
}

‎Coder-Desktop/CoderSDK/Deployment.swift

+14
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ public extension Client {
88
}
99
return try decode(BuildInfoResponse.self, from: res.data)
1010
}
11+
12+
func sshConfiguration() async throws(SDKError) -> SSHConfigResponse {
13+
let res = try await request("/api/v2/deployment/ssh", method: .get)
14+
guard res.resp.statusCode == 200 else {
15+
throw responseAsError(res)
16+
}
17+
return try decode(SSHConfigResponse.self, from: res.data)
18+
}
1119
}
1220

1321
public struct BuildInfoResponse: Codable, Equatable, Sendable {
@@ -20,3 +28,9 @@ public struct BuildInfoResponse: Codable, Equatable, Sendable {
2028
.flatMap { Range($0.range(at: 1), in: version).map { String(version[$0]) } }
2129
}
2230
}
31+
32+
public struct SSHConfigResponse: Codable, Equatable, Sendable {
33+
public let hostname_prefix: String?
34+
public let hostname_suffix: String?
35+
public let ssh_config_options: [String: String]
36+
}

0 commit comments

Comments
 (0)
Please sign in to comment.