mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-05 10:49:47 +00:00
totals, timer
This commit is contained in:
@@ -15,6 +15,7 @@ struct ServersSummaryView: View {
|
||||
@State private var selectedServerType: PresentedServerType = .smp
|
||||
@State private var selectedSMPServer: String? = nil
|
||||
@State private var selectedXFTPServer: String? = nil
|
||||
@State private var timer: Timer? = nil
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
enum PresentedUserCategory {
|
||||
@@ -33,9 +34,6 @@ struct ServersSummaryView: View {
|
||||
.navigationTitle("Servers info")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
reloadButton()
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
shareButton()
|
||||
}
|
||||
@@ -43,10 +41,25 @@ struct ServersSummaryView: View {
|
||||
}
|
||||
.onAppear {
|
||||
getServersSummary()
|
||||
startTimer()
|
||||
}
|
||||
.onDisappear {
|
||||
stopTimer()
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
private func startTimer() {
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
|
||||
getServersSummary()
|
||||
}
|
||||
}
|
||||
|
||||
func stopTimer() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
}
|
||||
|
||||
private func shareButton() -> some View {
|
||||
Button {
|
||||
if let serversSummary = serversSummary {
|
||||
@@ -65,14 +78,6 @@ struct ServersSummaryView: View {
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
private func reloadButton() -> some View {
|
||||
Button {
|
||||
getServersSummary()
|
||||
} label: {
|
||||
Image(systemName: "arrow.counterclockwise")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func viewBody() -> some View {
|
||||
if let summ = serversSummary {
|
||||
List {
|
||||
@@ -84,8 +89,8 @@ struct ServersSummaryView: View {
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
Picker("Server type", selection: $selectedServerType) {
|
||||
Text("SMP").tag(PresentedServerType.smp)
|
||||
Text("XFTP").tag(PresentedServerType.xftp)
|
||||
Text("Messages").tag(PresentedServerType.smp)
|
||||
Text("Files").tag(PresentedServerType.xftp)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
@@ -95,59 +100,71 @@ struct ServersSummaryView: View {
|
||||
|
||||
switch (selectedUserCategory, selectedServerType) {
|
||||
case (.allUsers, .smp):
|
||||
if summ.allUsedSMP.count > 0 || summ.allPrevSMP.count > 0 || summ.allProxSMP.count > 0 {
|
||||
if summ.allUsedSMP.count > 0 {
|
||||
smpServersListView(summ.allUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.allPrevSMP.count > 0 {
|
||||
smpServersListView(summ.allPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.allProxSMP.count > 0 {
|
||||
smpServersListView(summ.allProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
|
||||
SMPSubsView(subs: summ.allSMPTotal.subs)
|
||||
|
||||
ServerSessionsView(sess: summ.allSMPTotal.sessions)
|
||||
|
||||
if summ.allUsedSMP.count > 0 {
|
||||
smpServersListView(summ.allUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current app session")
|
||||
}
|
||||
if summ.allPrevSMP.count > 0 {
|
||||
smpServersListView(summ.allPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.allProxSMP.count > 0 {
|
||||
smpServersListView(summ.allProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
|
||||
SMPStatsView(stats: summ.allSMPTotal.stats, statsStartedAt: summ.statsStartedAt)
|
||||
|
||||
resetStatsButtonSection()
|
||||
case (.currentUser, .smp):
|
||||
if summ.userUsedSMP.count > 0 || summ.userPrevSMP.count > 0 || summ.userProxSMP.count > 0 {
|
||||
if summ.userUsedSMP.count > 0 {
|
||||
smpServersListView(summ.userUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.userPrevSMP.count > 0 {
|
||||
smpServersListView(summ.userPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.userProxSMP.count > 0 {
|
||||
smpServersListView(summ.userProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
|
||||
SMPSubsView(subs: summ.userSMPTotal.subs)
|
||||
|
||||
ServerSessionsView(sess: summ.userSMPTotal.sessions)
|
||||
|
||||
if summ.userUsedSMP.count > 0 {
|
||||
smpServersListView(summ.userUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current app session")
|
||||
}
|
||||
if summ.userPrevSMP.count > 0 {
|
||||
smpServersListView(summ.userPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.userProxSMP.count > 0 {
|
||||
smpServersListView(summ.userProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
|
||||
SMPStatsView(stats: summ.userSMPTotal.stats, statsStartedAt: summ.statsStartedAt)
|
||||
|
||||
resetStatsButtonSection()
|
||||
case (.allUsers, .xftp):
|
||||
if summ.allUsedXFTP.count > 0 || summ.allPrevXFTP.count > 0 {
|
||||
if summ.allUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.allUsedXFTP, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.allPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.allPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
|
||||
ServerSessionsView(sess: summ.allXFTPTotal.sessions)
|
||||
|
||||
if summ.allUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.allUsedXFTP, summ.statsStartedAt, "Current app session")
|
||||
}
|
||||
if summ.allPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.allPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
|
||||
XFTPStatsView(stats: summ.allXFTPTotal.stats, statsStartedAt: summ.statsStartedAt)
|
||||
|
||||
resetStatsButtonSection()
|
||||
case (.currentUser, .xftp):
|
||||
if summ.userUsedXFTP.count > 0 || summ.userPrevXFTP.count > 0 {
|
||||
if summ.userUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.userUsedXFTP, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.userPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.userPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
|
||||
ServerSessionsView(sess: summ.userXFTPTotal.sessions)
|
||||
|
||||
if summ.userUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.userUsedXFTP, summ.statsStartedAt, "Current app session")
|
||||
}
|
||||
if summ.userPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.userPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
|
||||
XFTPStatsView(stats: summ.userXFTPTotal.stats, statsStartedAt: summ.statsStartedAt)
|
||||
|
||||
resetStatsButtonSection()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -240,18 +257,6 @@ struct ServersSummaryView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func noCategoryInfoText() -> some View {
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.foregroundColor(Color.clear)
|
||||
Text("No info")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
|
||||
private func resetStatsButtonSection() -> some View {
|
||||
Section {
|
||||
Button {
|
||||
@@ -383,13 +388,13 @@ struct SMPServerSummaryView: View {
|
||||
Section {
|
||||
Text(summary.smpServer)
|
||||
.textSelection(.enabled)
|
||||
if let known = summary.known, !known {
|
||||
Button {
|
||||
// TODO
|
||||
} label: {
|
||||
Text("TODO Add as known")
|
||||
}
|
||||
}
|
||||
// if let known = summary.known, !known {
|
||||
// Button {
|
||||
// // TODO
|
||||
// } label: {
|
||||
// Text("Add as known")
|
||||
// }
|
||||
// }
|
||||
} header: {
|
||||
Text("Server address")
|
||||
} footer: {
|
||||
@@ -403,15 +408,15 @@ struct SMPServerSummaryView: View {
|
||||
}
|
||||
|
||||
if let subs = summary.subs {
|
||||
subsSection(subs)
|
||||
SMPSubsView(subs: subs)
|
||||
}
|
||||
|
||||
if let sess = summary.sessions {
|
||||
sessionsSection(sess)
|
||||
ServerSessionsView(sess: sess)
|
||||
}
|
||||
|
||||
if let stats = summary.stats {
|
||||
statsSection(stats)
|
||||
SMPStatsView(stats: stats, statsStartedAt: statsStartedAt)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
@@ -448,28 +453,42 @@ struct SMPServerSummaryView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func subsSection(_ subs: SMPServerSubs) -> some View {
|
||||
struct SMPSubsView: View {
|
||||
var subs: SMPServerSubs
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
infoRow("Active", "\(subs.ssActive)")
|
||||
infoRow("Pending", "\(subs.ssPending)")
|
||||
infoRow("Total", "\(subs.ssActive + subs.ssPending)")
|
||||
} header: {
|
||||
HStack {
|
||||
Text("Subscriptions")
|
||||
Text("Message subscriptions")
|
||||
SubscriptionStatusView(activeSubs: subs.ssActive, pendingSubs: subs.ssPending)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sessionsSection(_ sess: ServerSessions) -> some View {
|
||||
Section("Sessions") {
|
||||
struct ServerSessionsView: View {
|
||||
var sess: ServerSessions
|
||||
|
||||
var body: some View {
|
||||
Section("Transport sessions") {
|
||||
infoRow("Connected", "\(sess.ssConnected)")
|
||||
infoRow("Errors", "\(sess.ssErrors)")
|
||||
infoRow("Connecting", "\(sess.ssConnecting)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func statsSection(_ stats: AgentSMPServerStatsData) -> some View {
|
||||
struct SMPStatsView: View {
|
||||
var stats: AgentSMPServerStatsData
|
||||
var statsStartedAt: Date
|
||||
|
||||
var body: some View {
|
||||
Section("Statistics") {
|
||||
infoRow("Starting from", localTimestamp(statsStartedAt))
|
||||
infoRow("Messages sent directly", "\(stats._sentDirect)")
|
||||
@@ -488,10 +507,14 @@ struct SMPServerSummaryView: View {
|
||||
indentedInfoRow("other errors", "\(stats._recvErrs)")
|
||||
infoRow("Messages acknowledged", "\(stats._ackMsgs)")
|
||||
indentedInfoRow("attempts", "\(stats._ackAttempts)")
|
||||
indentedInfoRow("NO_MSG errors", "\(stats._ackNoMsgErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._ackOtherErrs)")
|
||||
infoRow("Connections created", "\(stats._connCreated)")
|
||||
indentedInfoRow("secured", "\(stats._connSecured)")
|
||||
indentedInfoRow("completed", "\(stats._connCompleted)")
|
||||
infoRow("Connections deleted", "\(stats._connDeleted)")
|
||||
indentedInfoRow("attempts", "\(stats._connDelAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._connDelErrs)")
|
||||
infoRow("Connections subscribed", "\(stats._connSubscribed)")
|
||||
indentedInfoRow("attempts", "\(stats._connSubAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._connSubErrs)")
|
||||
@@ -518,13 +541,13 @@ struct XFTPServerSummaryView: View {
|
||||
Section {
|
||||
Text(summary.xftpServer)
|
||||
.textSelection(.enabled)
|
||||
if let known = summary.known, !known {
|
||||
Button {
|
||||
// TODO
|
||||
} label: {
|
||||
Text("TODO Add as known")
|
||||
}
|
||||
}
|
||||
// if let known = summary.known, !known {
|
||||
// Button {
|
||||
// // TODO
|
||||
// } label: {
|
||||
// Text("Add as known")
|
||||
// }
|
||||
// }
|
||||
} header: {
|
||||
Text("Server address")
|
||||
} footer: {
|
||||
@@ -534,25 +557,17 @@ struct XFTPServerSummaryView: View {
|
||||
}
|
||||
|
||||
if let sess = summary.sessions {
|
||||
sessionsSection(sess)
|
||||
ServerSessionsView(sess: sess)
|
||||
}
|
||||
|
||||
inProgressSection()
|
||||
|
||||
if let stats = summary.stats {
|
||||
statsSection(stats)
|
||||
XFTPStatsView(stats: stats, statsStartedAt: statsStartedAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sessionsSection(_ sess: ServerSessions) -> some View {
|
||||
Section("Sessions") {
|
||||
infoRow("Connected", "\(sess.ssConnected)")
|
||||
infoRow("Errors", "\(sess.ssErrors)")
|
||||
infoRow("Connecting", "\(sess.ssConnecting)")
|
||||
}
|
||||
}
|
||||
|
||||
private func inProgressSection() -> some View {
|
||||
Section("In progress") {
|
||||
localizedInfoRow("Download", boolYesNo(summary.rcvInProgress))
|
||||
@@ -564,14 +579,24 @@ struct XFTPServerSummaryView: View {
|
||||
private func boolYesNo(_ b: Bool) -> LocalizedStringKey {
|
||||
b ? "yes" : "no"
|
||||
}
|
||||
}
|
||||
|
||||
private func statsSection(_ stats: AgentXFTPServerStatsData) -> some View {
|
||||
struct XFTPStatsView: View {
|
||||
var stats: AgentXFTPServerStatsData
|
||||
var statsStartedAt: Date
|
||||
|
||||
var body: some View {
|
||||
let kb: Int64 = 1024
|
||||
let prettyUploadsSize = ByteCountFormatter.string(fromByteCount: stats._uploadsSize * kb, countStyle: .binary)
|
||||
let prettyDownloadsSize = ByteCountFormatter.string(fromByteCount: stats._downloadsSize * kb, countStyle: .binary)
|
||||
Section("Statistics") {
|
||||
infoRow("Starting from", localTimestamp(statsStartedAt))
|
||||
infoRow("Chunks uploaded", "\(stats._uploads)")
|
||||
indentedInfoRow("size", prettyUploadsSize)
|
||||
indentedInfoRow("attempts", "\(stats._uploadAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._uploadErrs)")
|
||||
infoRow("Chunks downloaded", "\(stats._downloads)")
|
||||
indentedInfoRow("size", prettyDownloadsSize)
|
||||
indentedInfoRow("attempts", "\(stats._downloadAttempts)")
|
||||
indentedInfoRow("AUTH errors", "\(stats._downloadAuthErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._downloadErrs)")
|
||||
|
||||
@@ -2255,29 +2255,41 @@ public struct PresentedServersSummary: Codable {
|
||||
public var currentUserServers: ServersSummary
|
||||
public var allUsersServers: ServersSummary
|
||||
|
||||
public var allSMPTotal: SMPTotals { self.allUsersServers.smpTotals }
|
||||
public var allUsedSMP: [SMPServerSummary] { self.allUsersServers.currentlyUsedSMPServers }
|
||||
public var allPrevSMP: [SMPServerSummary] { self.allUsersServers.previouslyUsedSMPServers }
|
||||
public var allProxSMP: [SMPServerSummary] { self.allUsersServers.onlyProxiedSMPServers }
|
||||
|
||||
public var userSMPTotal: SMPTotals { self.currentUserServers.smpTotals }
|
||||
public var userUsedSMP: [SMPServerSummary] { self.currentUserServers.currentlyUsedSMPServers }
|
||||
public var userPrevSMP: [SMPServerSummary] { self.currentUserServers.previouslyUsedSMPServers }
|
||||
public var userProxSMP: [SMPServerSummary] { self.currentUserServers.onlyProxiedSMPServers }
|
||||
|
||||
public var allXFTPTotal: XFTPTotals { self.allUsersServers.xftpTotals }
|
||||
public var allUsedXFTP: [XFTPServerSummary] { self.allUsersServers.currentlyUsedXFTPServers }
|
||||
public var allPrevXFTP: [XFTPServerSummary] { self.allUsersServers.previouslyUsedXFTPServers }
|
||||
|
||||
public var userXFTPTotal: XFTPTotals { self.currentUserServers.xftpTotals }
|
||||
public var userUsedXFTP: [XFTPServerSummary] { self.currentUserServers.currentlyUsedXFTPServers }
|
||||
public var userPrevXFTP: [XFTPServerSummary] { self.currentUserServers.previouslyUsedXFTPServers }
|
||||
}
|
||||
|
||||
public struct ServersSummary: Codable {
|
||||
public var smpTotals: SMPTotals
|
||||
public var currentlyUsedSMPServers: [SMPServerSummary]
|
||||
public var previouslyUsedSMPServers: [SMPServerSummary]
|
||||
public var onlyProxiedSMPServers: [SMPServerSummary]
|
||||
public var xftpTotals: XFTPTotals
|
||||
public var currentlyUsedXFTPServers: [XFTPServerSummary]
|
||||
public var previouslyUsedXFTPServers: [XFTPServerSummary]
|
||||
}
|
||||
|
||||
public struct SMPTotals: Codable {
|
||||
public var sessions: ServerSessions
|
||||
public var subs: SMPServerSubs
|
||||
public var stats: AgentSMPServerStatsData
|
||||
}
|
||||
|
||||
public struct SMPServerSummary: Codable, Identifiable {
|
||||
public var smpServer: String
|
||||
public var known: Bool?
|
||||
@@ -2305,29 +2317,38 @@ public struct SMPServerSubs: Codable {
|
||||
}
|
||||
|
||||
public struct AgentSMPServerStatsData: Codable {
|
||||
public var _sentDirect: Int
|
||||
public var _sentViaProxy: Int
|
||||
public var _sentProxied: Int
|
||||
public var _sentDirectAttempts: Int
|
||||
public var _sentViaProxyAttempts: Int
|
||||
public var _sentProxiedAttempts: Int
|
||||
public var _sentAuthErrs: Int
|
||||
public var _sentQuotaErrs: Int
|
||||
public var _sentExpiredErrs: Int
|
||||
public var _sentOtherErrs: Int
|
||||
public var _recvMsgs: Int
|
||||
public var _recvDuplicates: Int
|
||||
public var _recvCryptoErrs: Int
|
||||
public var _recvErrs: Int
|
||||
public var _ackMsgs: Int
|
||||
public var _ackAttempts: Int
|
||||
public var _connCreated: Int
|
||||
public var _connSecured: Int
|
||||
public var _connCompleted: Int
|
||||
public var _connDeleted: Int
|
||||
public var _connSubscribed: Int
|
||||
public var _connSubAttempts: Int
|
||||
public var _connSubErrs: Int
|
||||
public var _sentDirect: Int64
|
||||
public var _sentViaProxy: Int64
|
||||
public var _sentProxied: Int64
|
||||
public var _sentDirectAttempts: Int64
|
||||
public var _sentViaProxyAttempts: Int64
|
||||
public var _sentProxiedAttempts: Int64
|
||||
public var _sentAuthErrs: Int64
|
||||
public var _sentQuotaErrs: Int64
|
||||
public var _sentExpiredErrs: Int64
|
||||
public var _sentOtherErrs: Int64
|
||||
public var _recvMsgs: Int64
|
||||
public var _recvDuplicates: Int64
|
||||
public var _recvCryptoErrs: Int64
|
||||
public var _recvErrs: Int64
|
||||
public var _ackMsgs: Int64
|
||||
public var _ackAttempts: Int64
|
||||
public var _ackNoMsgErrs: Int64
|
||||
public var _ackOtherErrs: Int64
|
||||
public var _connCreated: Int64
|
||||
public var _connSecured: Int64
|
||||
public var _connCompleted: Int64
|
||||
public var _connDeleted: Int64
|
||||
public var _connDelAttempts: Int64
|
||||
public var _connDelErrs: Int64
|
||||
public var _connSubscribed: Int64
|
||||
public var _connSubAttempts: Int64
|
||||
public var _connSubErrs: Int64
|
||||
}
|
||||
|
||||
public struct XFTPTotals: Codable {
|
||||
public var sessions: ServerSessions
|
||||
public var stats: AgentXFTPServerStatsData
|
||||
}
|
||||
|
||||
public struct XFTPServerSummary: Codable, Identifiable {
|
||||
@@ -2343,14 +2364,16 @@ public struct XFTPServerSummary: Codable, Identifiable {
|
||||
}
|
||||
|
||||
public struct AgentXFTPServerStatsData: Codable {
|
||||
public var _uploads: Int
|
||||
public var _uploadAttempts: Int
|
||||
public var _uploadErrs: Int
|
||||
public var _downloads: Int
|
||||
public var _downloadAttempts: Int
|
||||
public var _downloadAuthErrs: Int
|
||||
public var _downloadErrs: Int
|
||||
public var _deletions: Int
|
||||
public var _deleteAttempts: Int
|
||||
public var _deleteErrs: Int
|
||||
public var _uploads: Int64
|
||||
public var _uploadsSize: Int64
|
||||
public var _uploadAttempts: Int64
|
||||
public var _uploadErrs: Int64
|
||||
public var _downloads: Int64
|
||||
public var _downloadsSize: Int64
|
||||
public var _downloadAttempts: Int64
|
||||
public var _downloadAuthErrs: Int64
|
||||
public var _downloadErrs: Int64
|
||||
public var _deletions: Int64
|
||||
public var _deleteAttempts: Int64
|
||||
public var _deleteErrs: Int64
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user