Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Cloudflare Tunnel: connect to a database behind Cloudflare Access by letting TablePro start and stop `cloudflared access tcp` for you, the same way it manages SSH tunnels. Configure it per connection with browser sign-in or a service token. Needs cloudflared installed (`brew install cloudflared`). (#1285)
- Fill Column: right-click a column header and choose Fill Column to set one value across all loaded rows. The change is staged like a normal edit, so you review it and Save before it applies, and one undo reverts the whole fill. Not available on primary key columns. (#1304)
- AWS IAM authentication for PostgreSQL and MySQL connections to RDS and Aurora. Pick AWS IAM in the connection's Authentication field and use an access key, a named AWS profile, or SSO. TablePro generates a fresh login token on every connect and reconnect, so you never paste an expiring token, and SSL is required automatically. (#1291)

Expand Down
3 changes: 3 additions & 0 deletions TablePro/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class AppDelegate: NSObject, NSApplicationDelegate {
UserDefaults.standard.set(passwordSyncExpected, forKey: KeychainHelper.passwordSyncEnabledKey)
DatabaseManager.shared.startObservingSystemEvents()

Task { await CloudflareTunnelManager.shared.sweepStalePidsIfNeeded() }

MemoryPressureAdvisor.startMonitoring()
PluginManager.shared.loadPlugins()
UNUserNotificationCenter.current().delegate = self
Expand Down Expand Up @@ -136,6 +138,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
LinkedFolderWatcher.shared.stop()
SQLFolderWatcher.shared.stop()
SSHTunnelManager.shared.terminateAllProcessesSync()
CloudflareTunnelManager.shared.terminateAllProcessesSync()
}

@objc func handleSystemDidWake(_ notification: Notification) {
Expand Down
40 changes: 40 additions & 0 deletions TablePro/Core/Cloudflare/CloudflareTunnelError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// CloudflareTunnelError.swift
// TablePro
//

import Foundation

/// Errors raised while starting or supervising a cloudflared Access TCP tunnel.
enum CloudflareTunnelError: Error, LocalizedError, Equatable {
case binaryNotFound
case noAvailablePort
case startupFailed(stderrTail: String)
case readinessTimeout(stderrTail: String)
case browserAuthRequired(url: String)
case mutualExclusivityViolation
case tunnelAlreadyExists(UUID)

var errorDescription: String? {
switch self {
case .binaryNotFound:
return String(localized: "cloudflared was not found. Install it with `brew install cloudflared`, or set its path in the connection's Cloudflare Tunnel settings.")
case .noAvailablePort:
return String(localized: "No available local port for the Cloudflare tunnel.")
case .startupFailed(let stderrTail):
return stderrTail.isEmpty
? String(localized: "cloudflared failed to start.")
: String(format: String(localized: "cloudflared failed to start: %@"), stderrTail)
case .readinessTimeout(let stderrTail):
return stderrTail.isEmpty
? String(localized: "The Cloudflare tunnel did not become ready in time.")
: String(format: String(localized: "The Cloudflare tunnel did not become ready in time: %@"), stderrTail)
case .browserAuthRequired(let url):
return String(format: String(localized: "Cloudflare Access needs a browser sign-in. Sign in at %@, then reconnect."), url)
case .mutualExclusivityViolation:
return String(localized: "A connection cannot use SSH and Cloudflare tunnels at the same time.")
case .tunnelAlreadyExists(let id):
return String(format: String(localized: "A Cloudflare tunnel already exists for connection: %@"), id.uuidString)
}
}
}
Loading
Loading