338 lines
10 KiB
Swift
338 lines
10 KiB
Swift
//
|
|
// NetworkView.swift
|
|
// punchnet
|
|
//
|
|
// Created by 安礼成 on 2026/1/16.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct NetworkView: View {
|
|
@Environment(UserContext.self) var userContext: UserContext
|
|
@State private var networkModel = NetworkModel()
|
|
@State private var showMode: ShowMode = .resource
|
|
|
|
// 展示状态
|
|
enum ShowMode {
|
|
case resource
|
|
case device
|
|
}
|
|
|
|
var body: some View {
|
|
VStack {
|
|
HStack {
|
|
|
|
VStack {
|
|
HStack(alignment: .center) {
|
|
Text(userContext.networkSession?.networkName ?? "未知")
|
|
Text(">")
|
|
Spacer()
|
|
}
|
|
|
|
HStack {
|
|
Toggle("", isOn: $networkModel.isOn)
|
|
.toggleStyle(SwitchToggleStyle(tint: .green))
|
|
.disabled(true)
|
|
|
|
Text("已连接")
|
|
|
|
Spacer()
|
|
}
|
|
}
|
|
.frame(width: 320)
|
|
|
|
// 显示设备和资源选项
|
|
HStack {
|
|
Button {
|
|
self.showMode = .resource
|
|
} label: {
|
|
Text("资源")
|
|
}
|
|
|
|
Button {
|
|
self.showMode = .device
|
|
} label: {
|
|
Text("设备")
|
|
}
|
|
}
|
|
Spacer()
|
|
}
|
|
|
|
Group {
|
|
switch self.networkModel.connectState {
|
|
case .waitAuth:
|
|
NetworkWaitAuthView(networkModel: self.networkModel)
|
|
case .connected:
|
|
Group {
|
|
switch self.showMode {
|
|
case .resource:
|
|
NetworkResourceGroupView(networkModel: self.networkModel)
|
|
case .device:
|
|
NetworkDeviceGroupView(networkModel: self.networkModel)
|
|
}
|
|
}
|
|
case .disconnected:
|
|
NetworkDisconnctedView(networkModel: self.networkModel)
|
|
}
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.top, 10)
|
|
.padding(.leading, 10)
|
|
.toolbar {
|
|
ToolbarItem(placement: .primaryAction) {
|
|
Button {
|
|
print("clicked")
|
|
} label: {
|
|
Image(systemName: "gearshape")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 网络处于未连接状态
|
|
struct NetworkDisconnctedView: View {
|
|
@Bindable var networkModel: NetworkModel
|
|
@Environment(UserContext.self) var userContext: UserContext
|
|
|
|
@State private var showAlert = false
|
|
@State private var errorMessage = ""
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
Color.clear
|
|
|
|
VStack {
|
|
Button {
|
|
Task { @MainActor in
|
|
do {
|
|
try await self.connect()
|
|
try await self.startVpn()
|
|
} catch let err {
|
|
self.showAlert = true
|
|
self.errorMessage = err.localizedDescription
|
|
}
|
|
}
|
|
} label: {
|
|
Text("连接")
|
|
.font(.system(size: 14, weight: .regular))
|
|
.padding([.top, .bottom], 8)
|
|
.padding([.leading, .trailing], 30)
|
|
.foregroundColor(.white)
|
|
|
|
}
|
|
.background(Color(red: 74/255, green: 207/255, blue: 154/255))
|
|
.cornerRadius(5)
|
|
.frame(width: 120, height: 35)
|
|
|
|
Button {
|
|
Task {
|
|
try await VPNManager.shared.disableVpn()
|
|
}
|
|
} label: {
|
|
Text("关闭")
|
|
.font(.system(size: 14, weight: .regular))
|
|
.padding([.top, .bottom], 8)
|
|
.padding([.leading, .trailing], 30)
|
|
.foregroundColor(.white)
|
|
|
|
}
|
|
.background(Color(red: 74/255, green: 207/255, blue: 154/255))
|
|
.cornerRadius(5)
|
|
.frame(width: 120, height: 35)
|
|
}
|
|
}
|
|
.alert(isPresented: $showAlert) {
|
|
Alert(title: Text("提示"), message: Text(self.errorMessage))
|
|
}
|
|
|
|
}
|
|
|
|
private func connect() async throws {
|
|
guard let networkSession = userContext.networkSession else {
|
|
return
|
|
}
|
|
try await networkModel.connect(networkSession: networkSession)
|
|
}
|
|
|
|
// 执行登陆操作
|
|
private func startVpn() async throws {
|
|
guard let networkSession = userContext.networkSession else {
|
|
return
|
|
}
|
|
|
|
let options = SystemConfig.getOptions(networkId: UInt32(networkSession.networkId),
|
|
networkDomain: networkSession.networkDomain,
|
|
ip: "",
|
|
maskLen: 24,
|
|
accessToken: networkSession.accessToken,
|
|
identityId: 1234,
|
|
hostname: "mysql",
|
|
noticePort: 1234)
|
|
// token存在则优先使用token
|
|
try await VPNManager.shared.enableVpn(options: options!)
|
|
}
|
|
|
|
}
|
|
|
|
// 网络处于连接状态
|
|
|
|
// 显示资源信息
|
|
struct NetworkResourceGroupView: View {
|
|
@Bindable var networkModel: NetworkModel
|
|
|
|
var body: some View {
|
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: 3), spacing: 8) {
|
|
ForEach(self.networkModel.resourceList, id: \.id) { resource in
|
|
NetworkResourceView(resource: resource)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct NetworkResourceView: View {
|
|
var resource: Resource
|
|
|
|
var body: some View {
|
|
VStack {
|
|
HStack {
|
|
Text(resource.connectionStatus)
|
|
|
|
Text(resource.name)
|
|
.font(.system(size: 14, weight: .regular))
|
|
}
|
|
|
|
Text(resource.url)
|
|
.font(.system(size: 14, weight: .regular))
|
|
.padding(.leading, 30)
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
// 显示设备信息
|
|
struct NetworkDeviceGroupView: View {
|
|
@Bindable var networkModel: NetworkModel
|
|
@State private var selectedId: Int?
|
|
|
|
var body: some View {
|
|
NavigationSplitView {
|
|
List(self.networkModel.nodeList, id: \.id, selection: $selectedId) { node in
|
|
NetworkNodeHeadView(node: node)
|
|
}
|
|
.listStyle(.sidebar)
|
|
.onChange(of: selectedId) {
|
|
self.networkModel.changeSelectedNode(nodeId: selectedId)
|
|
}
|
|
.onAppear {
|
|
if selectedId == nil {
|
|
selectedId = self.networkModel.nodeList.first?.id
|
|
}
|
|
}
|
|
} detail: {
|
|
NetworkNodeDetailView(node: $networkModel.selectedNode)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
struct NetworkNodeHeadView: View {
|
|
var node: Node
|
|
|
|
var body: some View {
|
|
VStack {
|
|
HStack {
|
|
Text(node.connectionStatus)
|
|
|
|
Text(node.name)
|
|
.font(.system(size: 14, weight: .regular))
|
|
}
|
|
|
|
Text(node.ip)
|
|
.font(.system(size: 14, weight: .regular))
|
|
.padding(.leading, 30)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct NetworkNodeDetailView: View {
|
|
@Environment(UserContext.self) var userContext: UserContext
|
|
@Binding var node: Node?
|
|
@State private var resources: [Resource] = []
|
|
|
|
var body: some View {
|
|
Group {
|
|
if let node {
|
|
List {
|
|
Section {
|
|
HStack {
|
|
Text("连接状态")
|
|
Text("\(node.connectionStatus)")
|
|
Spacer()
|
|
}
|
|
|
|
HStack {
|
|
Text("虚拟IPv4")
|
|
Text("\(node.ip)")
|
|
Spacer()
|
|
}
|
|
|
|
HStack {
|
|
Text("操作系统")
|
|
Text(node.system ?? "未知")
|
|
Spacer()
|
|
}
|
|
}
|
|
|
|
Section("服务列表") {
|
|
ForEach(resources, id: \.id) { resource in
|
|
HStack {
|
|
Text("\(resource.name)")
|
|
Text("\(resource.url)")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.task {
|
|
await self.loadNodeResources()
|
|
}
|
|
} else {
|
|
EmptyView()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func loadNodeResources() async {
|
|
guard let networkSession = userContext.networkSession, let id = self.node?.id else {
|
|
return
|
|
}
|
|
|
|
let params: [String: Any] = [
|
|
"client_id": SystemConfig.getClientId(),
|
|
"access_token": networkSession.accessToken,
|
|
"id": id
|
|
]
|
|
|
|
if let nodeDetail = try? await SDLAPIClient.doPost(path: "/get_node_resources", params: params, as: NodeDetail.self) {
|
|
self.resources = nodeDetail.resourceList
|
|
}
|
|
}
|
|
}
|
|
|
|
struct NetworkWaitAuthView: View {
|
|
@Bindable var networkModel: NetworkModel
|
|
|
|
var body: some View {
|
|
Color.clear
|
|
.overlay {
|
|
Text("等待确认中")
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
NetworkView()
|
|
}
|