fix
This commit is contained in:
parent
fbbef96aa9
commit
f00f65985e
119
punchnet/Networking/SDLAPIClient+Network.swift
Normal file
119
punchnet/Networking/SDLAPIClient+Network.swift
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// SDLAPIClient+Network.swift
|
||||
// punchnet
|
||||
//
|
||||
// Created by 安礼成 on 2026/3/24.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
extension SDLAPIClient {
|
||||
|
||||
// 用来做临时的数据解析
|
||||
struct NetworkContext: Codable {
|
||||
let ip: String
|
||||
let maskLen: UInt8
|
||||
// 主机名称
|
||||
let hostname: String
|
||||
let identityId: UInt32
|
||||
let resourceList: [Resource]
|
||||
let nodeList: [Node]
|
||||
|
||||
// 资源列表
|
||||
struct Resource: Codable {
|
||||
var uuid = UUID().uuidString
|
||||
var id: Int
|
||||
var name: String
|
||||
var url: String
|
||||
var connectionStatus: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case url
|
||||
case connectionStatus = "connection_status"
|
||||
}
|
||||
}
|
||||
|
||||
// 设备列表
|
||||
struct Node: Codable {
|
||||
var id: Int
|
||||
var name: String
|
||||
var ip: String
|
||||
var system: String?
|
||||
var connectionStatus: String
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case ip
|
||||
case system
|
||||
case connectionStatus = "connection_status"
|
||||
}
|
||||
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case ip
|
||||
case maskLen = "mask_len"
|
||||
case hostname
|
||||
case identityId = "identity_id"
|
||||
case resourceList = "resource_list"
|
||||
case nodeList = "node_list"
|
||||
}
|
||||
|
||||
static func `default`() -> Self {
|
||||
return .init(ip: "", maskLen: 24, hostname: "", identityId: 0, resourceList: [], nodeList: [])
|
||||
}
|
||||
|
||||
// 节点详情
|
||||
struct NodeDetail: Codable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let ip: String
|
||||
let system: String?
|
||||
let connectionStatus: String
|
||||
let resourceList: [Resource]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case ip
|
||||
case system
|
||||
case connectionStatus = "connection_status"
|
||||
case resourceList = "resource_list"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static func connectNetwork(networkSession: NetworkSession) async throws -> NetworkContext {
|
||||
let params: [String: Any] = [
|
||||
"client_id": SystemConfig.getClientId(),
|
||||
"access_token": networkSession.accessToken
|
||||
]
|
||||
|
||||
return try await SDLAPIClient.doPost(path: "/connect", params: params, as: NetworkContext.self)
|
||||
}
|
||||
|
||||
static func loadNodeResources(accesToken: String, id: Int) async -> [NetworkContext.Resource] {
|
||||
let params: [String: Any] = [
|
||||
"client_id": SystemConfig.getClientId(),
|
||||
"access_token": accesToken,
|
||||
"id": id
|
||||
]
|
||||
|
||||
if let detail = try? await SDLAPIClient.doPost(path: "/get_node_resources", params: params, as: NetworkContext.NodeDetail.self) {
|
||||
return detail.resourceList
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
@ -13,7 +13,7 @@ class AppContext {
|
||||
var noticePort: Int
|
||||
|
||||
// 调用 "/connect" 之后的网络信息
|
||||
var networkContext: NetworkContext?
|
||||
var networkContext: SDLAPIClient.NetworkContext?
|
||||
|
||||
var loginCredit: Credit?
|
||||
var networkSession: SDLAPIClient.NetworkSession?
|
||||
|
||||
@ -8,96 +8,12 @@
|
||||
import Foundation
|
||||
import Observation
|
||||
|
||||
// 资源列表
|
||||
struct Resource: Codable {
|
||||
var uuid = UUID().uuidString
|
||||
var id: Int
|
||||
var name: String
|
||||
var url: String
|
||||
var connectionStatus: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case url
|
||||
case connectionStatus = "connection_status"
|
||||
}
|
||||
}
|
||||
|
||||
// 设备列表
|
||||
struct Node: Codable {
|
||||
var id: Int
|
||||
var name: String
|
||||
var ip: String
|
||||
var system: String?
|
||||
var connectionStatus: String
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case ip
|
||||
case system
|
||||
case connectionStatus = "connection_status"
|
||||
}
|
||||
|
||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
||||
return lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
// 用来做临时的数据解析
|
||||
struct NetworkContext: Codable {
|
||||
let ip: String
|
||||
let maskLen: UInt8
|
||||
// 主机名称
|
||||
let hostname: String
|
||||
let identityId: UInt32
|
||||
let resourceList: [Resource]
|
||||
let nodeList: [Node]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case ip
|
||||
case maskLen = "mask_len"
|
||||
case hostname
|
||||
case identityId = "identity_id"
|
||||
case resourceList = "resource_list"
|
||||
case nodeList = "node_list"
|
||||
}
|
||||
|
||||
static func `default`() -> Self {
|
||||
return .init(ip: "", maskLen: 24, hostname: "", identityId: 0, resourceList: [], nodeList: [])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 节点详情
|
||||
struct NodeDetail: Codable {
|
||||
let id: Int
|
||||
let name: String
|
||||
let ip: String
|
||||
let system: String?
|
||||
let connectionStatus: String
|
||||
let resourceList: [Resource]
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case ip
|
||||
case system
|
||||
case connectionStatus = "connection_status"
|
||||
case resourceList = "resource_list"
|
||||
}
|
||||
}
|
||||
|
||||
@Observable
|
||||
class NetworkModel {
|
||||
|
||||
// 当前选中的设备
|
||||
var selectedNode: Node?
|
||||
var networkContext: NetworkContext = .default()
|
||||
var selectedNode: SDLAPIClient.NetworkContext.Node?
|
||||
var networkContext: SDLAPIClient.NetworkContext = .default()
|
||||
|
||||
init() {
|
||||
|
||||
@ -111,15 +27,12 @@ class NetworkModel {
|
||||
}
|
||||
}
|
||||
|
||||
func connect(networkSession: SDLAPIClient.NetworkSession) async throws -> NetworkContext {
|
||||
func connect(networkSession: SDLAPIClient.NetworkSession) async throws {
|
||||
let params: [String: Any] = [
|
||||
"client_id": SystemConfig.getClientId(),
|
||||
"access_token": networkSession.accessToken
|
||||
]
|
||||
|
||||
self.networkContext = try await SDLAPIClient.doPost(path: "/connect", params: params, as: NetworkContext.self)
|
||||
|
||||
return self.networkContext
|
||||
self.networkContext = try await SDLAPIClient.connectNetwork(networkSession: networkSession)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@ struct NetworkView: View {
|
||||
@State private var networkModel = NetworkModel()
|
||||
@State private var showMode: NetworkShowMode = .resource
|
||||
@State private var connectState: ConnectState = .disconnected
|
||||
@State private var isConnecting: Bool = false
|
||||
|
||||
private var vpnManager = VPNManager.shared
|
||||
|
||||
@ -93,52 +92,9 @@ struct NetworkView: View {
|
||||
case .waitAuth:
|
||||
NetworkWaitAuthView(networkModel: networkModel)
|
||||
case .connected:
|
||||
if showMode == .resource {
|
||||
// 资源视图:网格布局
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [
|
||||
GridItem(.flexible(), spacing: 8),
|
||||
GridItem(.flexible(), spacing: 8),
|
||||
GridItem(.flexible(), spacing: 8)
|
||||
], spacing: 10) {
|
||||
ForEach(networkModel.networkContext.resourceList, id: \.uuid) { res in
|
||||
ResourceItemCard(resource: res)
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
}
|
||||
.transition(.opacity)
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
// 设备视图:双栏布局
|
||||
NetworkDeviceGroupView(networkModel: networkModel)
|
||||
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .opacity))
|
||||
}
|
||||
NetworkConnectedView(showMode: $showMode, networkModel: networkModel)
|
||||
case .disconnected:
|
||||
VStack(spacing: 20) {
|
||||
Spacer()
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.font(.system(size: 40, weight: .ultraLight))
|
||||
.foregroundStyle(.tertiary)
|
||||
.symbolEffect(.pulse, options: .repeating)
|
||||
|
||||
Text("尚未接入网络")
|
||||
.font(.headline)
|
||||
|
||||
Button(action: { startConnection() }) {
|
||||
if isConnecting {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
.frame(width: 80)
|
||||
} else {
|
||||
Text("建立安全连接")
|
||||
.frame(width: 80)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(isConnecting)
|
||||
Spacer()
|
||||
}
|
||||
NetworkDisconnectedView()
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
@ -162,16 +118,85 @@ struct NetworkView: View {
|
||||
@unknown default: connectState = .disconnected
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NetworkConnectedView: View {
|
||||
@Binding var showMode: NetworkShowMode
|
||||
@Bindable var networkModel: NetworkModel
|
||||
|
||||
var body: some View {
|
||||
if showMode == .resource {
|
||||
// 资源视图:网格布局
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [
|
||||
GridItem(.flexible(), spacing: 8),
|
||||
GridItem(.flexible(), spacing: 8),
|
||||
GridItem(.flexible(), spacing: 8)
|
||||
], spacing: 10) {
|
||||
ForEach(networkModel.networkContext.resourceList, id: \.uuid) { res in
|
||||
ResourceItemCard(resource: res)
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
}
|
||||
.transition(.opacity)
|
||||
.frame(maxWidth: .infinity)
|
||||
} else {
|
||||
// 设备视图:双栏布局
|
||||
NetworkDeviceGroupView(networkModel: networkModel)
|
||||
.transition(.asymmetric(insertion: .move(edge: .trailing), removal: .opacity))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct NetworkDisconnectedView: View {
|
||||
@State private var isConnecting: Bool = false
|
||||
@Environment(AppContext.self) private var appContext: AppContext
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
Spacer()
|
||||
Image(systemName: "antenna.radiowaves.left.and.right")
|
||||
.font(.system(size: 40, weight: .ultraLight))
|
||||
.foregroundStyle(.tertiary)
|
||||
.symbolEffect(.pulse, options: .repeating)
|
||||
|
||||
Text("尚未接入网络")
|
||||
.font(.headline)
|
||||
|
||||
Button(action: {
|
||||
Task { @MainActor in
|
||||
await startConnection()
|
||||
}
|
||||
}) {
|
||||
if isConnecting {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
.frame(width: 80)
|
||||
} else {
|
||||
Text("建立安全连接")
|
||||
.frame(width: 80)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(isConnecting)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
private func startConnection() async {
|
||||
self.isConnecting = true
|
||||
defer {
|
||||
self.isConnecting = false
|
||||
}
|
||||
|
||||
private func startConnection() {
|
||||
isConnecting = true
|
||||
Task {
|
||||
do {
|
||||
guard let session = appContext.networkSession else {
|
||||
return
|
||||
}
|
||||
|
||||
let context = try await networkModel.connect(networkSession: session)
|
||||
let context = try await SDLAPIClient.connectNetwork(networkSession: session)
|
||||
|
||||
// 登陆后需要保存到app的上线文
|
||||
self.appContext.networkContext = context
|
||||
@ -186,17 +211,13 @@ struct NetworkView: View {
|
||||
hostname: context.hostname,
|
||||
noticePort: appContext.noticePort
|
||||
) {
|
||||
try await vpnManager.enableVpn(options: options)
|
||||
try await VPNManager.shared.enableVpn(options: options)
|
||||
}
|
||||
} catch {
|
||||
print("Connection error: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
isConnecting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 设备组视图 (NavigationSplitView)
|
||||
@ -229,7 +250,7 @@ struct NetworkDeviceGroupView: View {
|
||||
|
||||
// MARK: - 子组件
|
||||
struct NetworkNodeHeadView: View {
|
||||
var node: Node
|
||||
var node: SDLAPIClient.NetworkContext.Node
|
||||
var body: some View {
|
||||
HStack(spacing: 10) {
|
||||
Circle()
|
||||
@ -252,8 +273,8 @@ struct NetworkNodeHeadView: View {
|
||||
struct NetworkNodeDetailView: View {
|
||||
@Environment(AppContext.self) private var appContext: AppContext
|
||||
|
||||
var node: Node
|
||||
@State private var resources: [Resource] = []
|
||||
var node: SDLAPIClient.NetworkContext.Node
|
||||
@State private var resources: [SDLAPIClient.NetworkContext.Resource] = []
|
||||
@State private var isLoading = false
|
||||
|
||||
var body: some View {
|
||||
@ -296,9 +317,9 @@ struct NetworkNodeDetailView: View {
|
||||
return
|
||||
}
|
||||
|
||||
isLoading = true
|
||||
self.isLoading = true
|
||||
defer {
|
||||
isLoading = false
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
let params: [String: Any] = [
|
||||
@ -307,14 +328,13 @@ struct NetworkNodeDetailView: View {
|
||||
"id": id
|
||||
]
|
||||
|
||||
if let detail = try? await SDLAPIClient.doPost(path: "/get_node_resources", params: params, as: NodeDetail.self) {
|
||||
self.resources = detail.resourceList
|
||||
}
|
||||
self.resources = await SDLAPIClient.loadNodeResources(accesToken: session.accessToken, id: id)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ResourceItemCard: View {
|
||||
let resource: Resource
|
||||
let resource: SDLAPIClient.NetworkContext.Resource
|
||||
@State private var isHovered = false
|
||||
|
||||
var body: some View {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user