diff --git a/ClashX Dashboard.xcodeproj/project.pbxproj b/ClashX Dashboard.xcodeproj/project.pbxproj index 0add89a..2d5ea24 100644 --- a/ClashX Dashboard.xcodeproj/project.pbxproj +++ b/ClashX Dashboard.xcodeproj/project.pbxproj @@ -20,12 +20,13 @@ 0172F1352A1FB0B900EE2B6D /* ConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */; }; 0172F1372A1FB0CD00EE2B6D /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */; }; 0172F1392A1FB0E900EE2B6D /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1382A1FB0E900EE2B6D /* Logger.swift */; }; - 0172F1412A1FB10D00EE2B6D /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13B2A1FB10C00EE2B6D /* ClashConnection.swift */; }; + 0172F1412A1FB10D00EE2B6D /* DBConnectionSnapShot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */; }; 0172F1422A1FB10D00EE2B6D /* ClashRuleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */; }; 0172F1432A1FB10D00EE2B6D /* ClashRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */; }; 0172F1442A1FB10D00EE2B6D /* ClashConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */; }; 0172F1452A1FB10D00EE2B6D /* ClashProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */; }; 0172F1462A1FB10D00EE2B6D /* ClashProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */; }; + 0172F1482A1FB90200EE2B6D /* ClashConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0172F1472A1FB90200EE2B6D /* ClashConnection.swift */; }; 017753C029EF7FB2006999DB /* APIServerItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017753BF29EF7FB1006999DB /* APIServerItem.swift */; }; 017DCADD29E83BFD00B9622A /* RuleProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 017DCADC29E83BFD00B9622A /* RuleProviderView.swift */; }; 017F9AAA2A0DFEBD00B81497 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 017F9AA92A0DFEBD00B81497 /* Introspect */; }; @@ -80,12 +81,13 @@ 0172F1342A1FB0B900EE2B6D /* ConfigManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigManager.swift; sourceTree = ""; }; 0172F1362A1FB0CD00EE2B6D /* ApiRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = ""; }; 0172F1382A1FB0E900EE2B6D /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - 0172F13B2A1FB10C00EE2B6D /* ClashConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConnection.swift; sourceTree = ""; }; + 0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DBConnectionSnapShot.swift; sourceTree = ""; }; 0172F13C2A1FB10D00EE2B6D /* ClashRuleProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRuleProvider.swift; sourceTree = ""; }; 0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashRule.swift; sourceTree = ""; }; 0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConfig.swift; sourceTree = ""; }; 0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProvider.swift; sourceTree = ""; }; 0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashProxy.swift; sourceTree = ""; }; + 0172F1472A1FB90200EE2B6D /* ClashConnection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClashConnection.swift; sourceTree = ""; }; 017753BF29EF7FB1006999DB /* APIServerItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIServerItem.swift; sourceTree = ""; }; 017DCADC29E83BFD00B9622A /* RuleProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleProviderView.swift; sourceTree = ""; }; 017F9AAB2A0E0B2300B81497 /* ProxyGroupRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyGroupRowView.swift; sourceTree = ""; }; @@ -179,7 +181,7 @@ isa = PBXGroup; children = ( 0172F13E2A1FB10D00EE2B6D /* ClashConfig.swift */, - 0172F13B2A1FB10C00EE2B6D /* ClashConnection.swift */, + 0172F1472A1FB90200EE2B6D /* ClashConnection.swift */, 0172F13F2A1FB10D00EE2B6D /* ClashProvider.swift */, 0172F1402A1FB10D00EE2B6D /* ClashProxy.swift */, 0172F13D2A1FB10D00EE2B6D /* ClashRule.swift */, @@ -344,6 +346,7 @@ children = ( 01A3EF032A120103003038B5 /* DBProxyStorage.swift */, 01505C492A147B84001ACC4F /* DBProviderStorage.swift */, + 0172F13B2A1FB10C00EE2B6D /* DBConnectionSnapShot.swift */, ); path = Models; sourceTree = ""; @@ -455,7 +458,7 @@ 0192318029DD5E0B00539EDD /* LogsView.swift in Sources */, 0172F1392A1FB0E900EE2B6D /* Logger.swift in Sources */, 01505C4E2A14AAEB001ACC4F /* ProviderProxiesView.swift in Sources */, - 0172F1412A1FB10D00EE2B6D /* ClashConnection.swift in Sources */, + 0172F1412A1FB10D00EE2B6D /* DBConnectionSnapShot.swift in Sources */, 0192318529DD7DCD00539EDD /* SidebarItemView.swift in Sources */, 0172F1442A1FB10D00EE2B6D /* ClashConfig.swift in Sources */, 0172F1452A1FB10D00EE2B6D /* ClashProvider.swift in Sources */, @@ -489,6 +492,7 @@ 015278082A15F9FD00516236 /* ProxyProviderInfoView.swift in Sources */, 0192317829DD5DA500539EDD /* ProxiesView.swift in Sources */, 01F885CF29DFD8DF008241EB /* CollectionsTableView.swift in Sources */, + 0172F1482A1FB90200EE2B6D /* ClashConnection.swift in Sources */, 018AFEA52A1B5F7A0076E66B /* ProgressButton.swift in Sources */, 01505C4A2A147B84001ACC4F /* DBProviderStorage.swift in Sources */, 0172CB5129E5AE670072DDEF /* SwiftUIViewExtensions.swift in Sources */, diff --git a/ClashX Dashboard/ClashX Links/ApiRequest.swift b/ClashX Dashboard/ClashX Links/ApiRequest.swift index abf86a2..55fc91d 100644 --- a/ClashX Dashboard/ClashX Links/ApiRequest.swift +++ b/ClashX Dashboard/ClashX Links/ApiRequest.swift @@ -284,24 +284,24 @@ class ApiRequest { // MARK: - Connections extension ApiRequest { - static func getConnections(completeHandler: @escaping (ClashConnectionSnapShot) -> Void) { + static func getConnections(completeHandler: @escaping (DBConnectionSnapShot) -> Void) { let decoder = JSONDecoder() decoder.dateDecodingStrategy = .formatted(DateFormatter.js) - req("/connections").responseDecodable(of: ClashConnectionSnapShot.self, decoder: decoder) { resp in + req("/connections").responseDecodable(of: DBConnectionSnapShot.self, decoder: decoder) { resp in switch resp.result { case let .success(snapshot): completeHandler(snapshot) case .failure: return // assertionFailure() -// completeHandler(ClashConnectionSnapShot()) +// completeHandler(DBConnectionSnapShot()) } } } - static func closeConnection(_ conn: ClashConnection) { + static func closeConnection(_ conn: ClashConnectionSnapShot.Connection) { req("/connections/".appending(conn.id), method: .delete).response { _ in } } diff --git a/ClashX Dashboard/ClashX Links/Models/ClashConnection.swift b/ClashX Dashboard/ClashX Links/Models/ClashConnection.swift index 196ecf4..0cc323d 100644 --- a/ClashX Dashboard/ClashX Links/Models/ClashConnection.swift +++ b/ClashX Dashboard/ClashX Links/Models/ClashConnection.swift @@ -7,104 +7,14 @@ // import Cocoa -import DifferenceKit struct ClashConnectionSnapShot: Codable { - let downloadTotal: Int - let uploadTotal: Int - let connections: [ClashConnection] + let connections: [Connection] } -struct ClashConnection: Codable, Hashable { - let id: String - let chains: [String] - let upload: Int64 - let download: Int64 - let start: Date - let rule: String - let rulePayload: String - - let metadata: MetaConnectionData -} - -struct MetaConnectionData: Codable, Hashable { - let uid: Int - - let network: String - let type: String - let sourceIP: String - let destinationIP: String - let sourcePort: String - let destinationPort: String - let inboundIP: String - let inboundPort: String - let inboundName: String - let host: String - let dnsMode: String - let process: String - let processPath: String - let specialProxy: String - let specialRules: String - let remoteDestination: String - let sniffHost: String - -} - - -class ClashConnectionObject: NSObject, Differentiable { - @objc let id: String - @objc let host: String - @objc let sniffHost: String - @objc let process: String - @objc let download: Int64 - @objc let upload: Int64 - let downloadString: String - let uploadString: String - let chains: [String] - @objc let chainString: String - @objc let ruleString: String - @objc let startDate: Date - let startString: String - @objc let source: String - @objc let destinationIP: String? - @objc let type: String - - var differenceIdentifier: String { - return id - } - - func isContentEqual(to source: ClashConnectionObject) -> Bool { - download == source.download && - upload == source.upload && - startString == source.startString - } - - init(_ conn: ClashConnection) { - let byteCountFormatter = ByteCountFormatter() - let startFormatter = RelativeDateTimeFormatter() - startFormatter.unitsStyle = .short - - let metadata = conn.metadata - - id = conn.id - host = "\(metadata.host == "" ? metadata.destinationIP : metadata.host):\(metadata.destinationPort)" - sniffHost = metadata.sniffHost == "" ? "-" : metadata.sniffHost - process = metadata.process - download = conn.download - downloadString = byteCountFormatter.string(fromByteCount: conn.download) - upload = conn.upload - uploadString = byteCountFormatter.string(fromByteCount: conn.upload) - chains = conn.chains - chainString = conn.chains.reversed().joined(separator: "/") - ruleString = conn.rulePayload == "" ? conn.rule : "\(conn.rule) :: \(conn.rulePayload)" - startDate = conn.start - startString = startFormatter.localizedString(for: conn.start, relativeTo: Date()) - source = "\(metadata.sourceIP):\(metadata.sourcePort)" - destinationIP = [metadata.remoteDestination, - metadata.destinationIP, - metadata.host].first(where: { $0 != "" }) - - type = "\(metadata.type)(\(metadata.network))" - } - +extension ClashConnectionSnapShot { + struct Connection: Codable { + let id: String + let chains: [String] + } } diff --git a/ClashX Dashboard/Models/DBConnectionSnapShot.swift b/ClashX Dashboard/Models/DBConnectionSnapShot.swift new file mode 100644 index 0000000..bdb4372 --- /dev/null +++ b/ClashX Dashboard/Models/DBConnectionSnapShot.swift @@ -0,0 +1,108 @@ +// +// DBConnectionSnapShot.swift +// ClashX Dashboard +// +// + +import Cocoa +import DifferenceKit + +struct DBConnectionSnapShot: Codable { + let downloadTotal: Int + let uploadTotal: Int + let connections: [DBConnection] +} + +struct DBConnection: Codable, Hashable { + let id: String + let chains: [String] + let upload: Int64 + let download: Int64 + let start: Date + let rule: String + let rulePayload: String + + let metadata: DBMetaConnectionData +} + +struct DBMetaConnectionData: Codable, Hashable { + let uid: Int + + let network: String + let type: String + let sourceIP: String + let destinationIP: String + let sourcePort: String + let destinationPort: String + let inboundIP: String + let inboundPort: String + let inboundName: String + let host: String + let dnsMode: String + let process: String + let processPath: String + let specialProxy: String + let specialRules: String + let remoteDestination: String + let sniffHost: String + +} + + +class DBConnectionObject: NSObject, Differentiable { + @objc let id: String + @objc let host: String + @objc let sniffHost: String + @objc let process: String + @objc let download: Int64 + @objc let upload: Int64 + let downloadString: String + let uploadString: String + let chains: [String] + @objc let chainString: String + @objc let ruleString: String + @objc let startDate: Date + let startString: String + @objc let source: String + @objc let destinationIP: String? + @objc let type: String + + var differenceIdentifier: String { + return id + } + + func isContentEqual(to source: DBConnectionObject) -> Bool { + download == source.download && + upload == source.upload && + startString == source.startString + } + + init(_ conn: DBConnection) { + let byteCountFormatter = ByteCountFormatter() + let startFormatter = RelativeDateTimeFormatter() + startFormatter.unitsStyle = .short + + let metadata = conn.metadata + + id = conn.id + host = "\(metadata.host == "" ? metadata.destinationIP : metadata.host):\(metadata.destinationPort)" + sniffHost = metadata.sniffHost == "" ? "-" : metadata.sniffHost + process = metadata.process + download = conn.download + downloadString = byteCountFormatter.string(fromByteCount: conn.download) + upload = conn.upload + uploadString = byteCountFormatter.string(fromByteCount: conn.upload) + chains = conn.chains + chainString = conn.chains.reversed().joined(separator: "/") + ruleString = conn.rulePayload == "" ? conn.rule : "\(conn.rule) :: \(conn.rulePayload)" + startDate = conn.start + startString = startFormatter.localizedString(for: conn.start, relativeTo: Date()) + source = "\(metadata.sourceIP):\(metadata.sourcePort)" + destinationIP = [metadata.remoteDestination, + metadata.destinationIP, + metadata.host].first(where: { $0 != "" }) + + type = "\(metadata.type)(\(metadata.network))" + } + +} diff --git a/ClashX Dashboard/Views/ClashApiDatasStorage.swift b/ClashX Dashboard/Views/ClashApiDatasStorage.swift index 8d41f48..6e75f96 100644 --- a/ClashX Dashboard/Views/ClashApiDatasStorage.swift +++ b/ClashX Dashboard/Views/ClashApiDatasStorage.swift @@ -148,5 +148,5 @@ class ClashLogStorage: ObservableObject { } class ClashConnsStorage: ObservableObject { - @Published var conns = [ClashConnection]() + @Published var conns = [DBConnection]() } diff --git a/ClashX Dashboard/Views/ContentTabs/Connections/CollectionsTableView.swift b/ClashX Dashboard/Views/ContentTabs/Connections/CollectionsTableView.swift index e352fe7..20e07ed 100644 --- a/ClashX Dashboard/Views/ContentTabs/Connections/CollectionsTableView.swift +++ b/ClashX Dashboard/Views/ContentTabs/Connections/CollectionsTableView.swift @@ -72,27 +72,27 @@ struct CollectionsTableView: NSViewRepresentable { switch $0 { case .host: - sort = .init(keyPath: \ClashConnectionObject.host, ascending: true) + sort = .init(keyPath: \DBConnectionObject.host, ascending: true) case .sniffHost: - sort = .init(keyPath: \ClashConnectionObject.sniffHost, ascending: true) + sort = .init(keyPath: \DBConnectionObject.sniffHost, ascending: true) case .process: - sort = .init(keyPath: \ClashConnectionObject.process, ascending: true) + sort = .init(keyPath: \DBConnectionObject.process, ascending: true) case .dl: - sort = .init(keyPath: \ClashConnectionObject.download, ascending: true) + sort = .init(keyPath: \DBConnectionObject.download, ascending: true) case .ul: - sort = .init(keyPath: \ClashConnectionObject.upload, ascending: true) + sort = .init(keyPath: \DBConnectionObject.upload, ascending: true) case .chain: - sort = .init(keyPath: \ClashConnectionObject.chainString, ascending: true) + sort = .init(keyPath: \DBConnectionObject.chainString, ascending: true) case .rule: - sort = .init(keyPath: \ClashConnectionObject.ruleString, ascending: true) + sort = .init(keyPath: \DBConnectionObject.ruleString, ascending: true) case .time: - sort = .init(keyPath: \ClashConnectionObject.startDate, ascending: true) + sort = .init(keyPath: \DBConnectionObject.startDate, ascending: true) case .source: - sort = .init(keyPath: \ClashConnectionObject.source, ascending: true) + sort = .init(keyPath: \DBConnectionObject.source, ascending: true) case .destinationIP: - sort = .init(keyPath: \ClashConnectionObject.destinationIP, ascending: true) + sort = .init(keyPath: \DBConnectionObject.destinationIP, ascending: true) case .type: - sort = .init(keyPath: \ClashConnectionObject.type, ascending: true) + sort = .init(keyPath: \DBConnectionObject.type, ascending: true) default: sort = nil } @@ -114,11 +114,11 @@ struct CollectionsTableView: NSViewRepresentable { func updateNSView(_ nsView: NSScrollView, context: Context) { context.coordinator.parent = self guard let tableView = nsView.documentView as? NSTableView, - let data = data as? [ClashConnection] else { + let data = data as? [DBConnection] else { return } - let target = updateSorts(data.map(ClashConnectionObject.init), tableView: tableView) + let target = updateSorts(data.map(DBConnectionObject.init), tableView: tableView) let source = context.coordinator.conns let changeset = StagedChangeset(source: source, target: target) @@ -129,12 +129,12 @@ struct CollectionsTableView: NSViewRepresentable { } } - func updateSorts(_ objects: [ClashConnectionObject], - tableView: NSTableView) -> [ClashConnectionObject] { + func updateSorts(_ objects: [DBConnectionObject], + tableView: NSTableView) -> [DBConnectionObject] { var re = objects var sortDescriptors = tableView.sortDescriptors - sortDescriptors.append(.init(keyPath: \ClashConnectionObject.id, ascending: true)) + sortDescriptors.append(.init(keyPath: \DBConnectionObject.id, ascending: true)) re = re.sorted(descriptors: sortDescriptors) let filterKeys = [ @@ -161,7 +161,7 @@ struct CollectionsTableView: NSViewRepresentable { var parent: CollectionsTableView - var conns = [ClashConnectionObject]() + var conns = [DBConnectionObject]() init(parent: CollectionsTableView) { self.parent = parent