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
|
var noticePort: Int
|
||||||
|
|
||||||
// 调用 "/connect" 之后的网络信息
|
// 调用 "/connect" 之后的网络信息
|
||||||
var networkContext: NetworkContext?
|
var networkContext: SDLAPIClient.NetworkContext?
|
||||||
|
|
||||||
var loginCredit: Credit?
|
var loginCredit: Credit?
|
||||||
var networkSession: SDLAPIClient.NetworkSession?
|
var networkSession: SDLAPIClient.NetworkSession?
|
||||||
|
|||||||
@ -8,96 +8,12 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Observation
|
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
|
@Observable
|
||||||
class NetworkModel {
|
class NetworkModel {
|
||||||
|
|
||||||
// 当前选中的设备
|
// 当前选中的设备
|
||||||
var selectedNode: Node?
|
var selectedNode: SDLAPIClient.NetworkContext.Node?
|
||||||
var networkContext: NetworkContext = .default()
|
var networkContext: SDLAPIClient.NetworkContext = .default()
|
||||||
|
|
||||||
init() {
|
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] = [
|
let params: [String: Any] = [
|
||||||
"client_id": SystemConfig.getClientId(),
|
"client_id": SystemConfig.getClientId(),
|
||||||
"access_token": networkSession.accessToken
|
"access_token": networkSession.accessToken
|
||||||
]
|
]
|
||||||
|
self.networkContext = try await SDLAPIClient.connectNetwork(networkSession: networkSession)
|
||||||
self.networkContext = try await SDLAPIClient.doPost(path: "/connect", params: params, as: NetworkContext.self)
|
|
||||||
|
|
||||||
return self.networkContext
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,7 +25,6 @@ struct NetworkView: View {
|
|||||||
@State private var networkModel = NetworkModel()
|
@State private var networkModel = NetworkModel()
|
||||||
@State private var showMode: NetworkShowMode = .resource
|
@State private var showMode: NetworkShowMode = .resource
|
||||||
@State private var connectState: ConnectState = .disconnected
|
@State private var connectState: ConnectState = .disconnected
|
||||||
@State private var isConnecting: Bool = false
|
|
||||||
|
|
||||||
private var vpnManager = VPNManager.shared
|
private var vpnManager = VPNManager.shared
|
||||||
|
|
||||||
@ -93,52 +92,9 @@ struct NetworkView: View {
|
|||||||
case .waitAuth:
|
case .waitAuth:
|
||||||
NetworkWaitAuthView(networkModel: networkModel)
|
NetworkWaitAuthView(networkModel: networkModel)
|
||||||
case .connected:
|
case .connected:
|
||||||
if showMode == .resource {
|
NetworkConnectedView(showMode: $showMode, networkModel: networkModel)
|
||||||
// 资源视图:网格布局
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
case .disconnected:
|
case .disconnected:
|
||||||
VStack(spacing: 20) {
|
NetworkDisconnectedView()
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
@ -162,41 +118,106 @@ struct NetworkView: View {
|
|||||||
@unknown default: connectState = .disconnected
|
@unknown default: connectState = .disconnected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NetworkConnectedView: View {
|
||||||
|
@Binding var showMode: NetworkShowMode
|
||||||
|
@Bindable var networkModel: NetworkModel
|
||||||
|
|
||||||
private func startConnection() {
|
var body: some View {
|
||||||
isConnecting = true
|
if showMode == .resource {
|
||||||
Task {
|
// 资源视图:网格布局
|
||||||
do {
|
ScrollView {
|
||||||
guard let session = appContext.networkSession else {
|
LazyVGrid(columns: [
|
||||||
return
|
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)
|
||||||
let context = try await networkModel.connect(networkSession: session)
|
|
||||||
|
|
||||||
// 登陆后需要保存到app的上线文
|
|
||||||
self.appContext.networkContext = context
|
|
||||||
|
|
||||||
if let options = SystemConfig.getOptions(
|
|
||||||
networkId: UInt32(session.networkId),
|
|
||||||
networkDomain: session.networkDomain,
|
|
||||||
ip: context.ip,
|
|
||||||
maskLen: context.maskLen,
|
|
||||||
accessToken: session.accessToken,
|
|
||||||
identityId: context.identityId,
|
|
||||||
hostname: context.hostname,
|
|
||||||
noticePort: appContext.noticePort
|
|
||||||
) {
|
|
||||||
try await vpnManager.enableVpn(options: options)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Connection error: \(error)")
|
|
||||||
}
|
|
||||||
|
|
||||||
await MainActor.run {
|
|
||||||
isConnecting = false
|
|
||||||
}
|
}
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
guard let session = appContext.networkSession else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let context = try await SDLAPIClient.connectNetwork(networkSession: session)
|
||||||
|
|
||||||
|
// 登陆后需要保存到app的上线文
|
||||||
|
self.appContext.networkContext = context
|
||||||
|
|
||||||
|
if let options = SystemConfig.getOptions(
|
||||||
|
networkId: UInt32(session.networkId),
|
||||||
|
networkDomain: session.networkDomain,
|
||||||
|
ip: context.ip,
|
||||||
|
maskLen: context.maskLen,
|
||||||
|
accessToken: session.accessToken,
|
||||||
|
identityId: context.identityId,
|
||||||
|
hostname: context.hostname,
|
||||||
|
noticePort: appContext.noticePort
|
||||||
|
) {
|
||||||
|
try await VPNManager.shared.enableVpn(options: options)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Connection error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 设备组视图 (NavigationSplitView)
|
// MARK: - 设备组视图 (NavigationSplitView)
|
||||||
@ -229,7 +250,7 @@ struct NetworkDeviceGroupView: View {
|
|||||||
|
|
||||||
// MARK: - 子组件
|
// MARK: - 子组件
|
||||||
struct NetworkNodeHeadView: View {
|
struct NetworkNodeHeadView: View {
|
||||||
var node: Node
|
var node: SDLAPIClient.NetworkContext.Node
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
Circle()
|
Circle()
|
||||||
@ -252,8 +273,8 @@ struct NetworkNodeHeadView: View {
|
|||||||
struct NetworkNodeDetailView: View {
|
struct NetworkNodeDetailView: View {
|
||||||
@Environment(AppContext.self) private var appContext: AppContext
|
@Environment(AppContext.self) private var appContext: AppContext
|
||||||
|
|
||||||
var node: Node
|
var node: SDLAPIClient.NetworkContext.Node
|
||||||
@State private var resources: [Resource] = []
|
@State private var resources: [SDLAPIClient.NetworkContext.Resource] = []
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -296,9 +317,9 @@ struct NetworkNodeDetailView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = true
|
self.isLoading = true
|
||||||
defer {
|
defer {
|
||||||
isLoading = false
|
self.isLoading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
let params: [String: Any] = [
|
let params: [String: Any] = [
|
||||||
@ -307,14 +328,13 @@ struct NetworkNodeDetailView: View {
|
|||||||
"id": id
|
"id": id
|
||||||
]
|
]
|
||||||
|
|
||||||
if let detail = try? await SDLAPIClient.doPost(path: "/get_node_resources", params: params, as: NodeDetail.self) {
|
self.resources = await SDLAPIClient.loadNodeResources(accesToken: session.accessToken, id: id)
|
||||||
self.resources = detail.resourceList
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResourceItemCard: View {
|
struct ResourceItemCard: View {
|
||||||
let resource: Resource
|
let resource: SDLAPIClient.NetworkContext.Resource
|
||||||
@State private var isHovered = false
|
@State private var isHovered = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user