Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -297,11 +297,21 @@ jobs:
working-directory: apps/web
run: bun run tsc -b

- name: Type check widget SDK
if: needs.changes.outputs.web == 'true'
working-directory: packages/widget-sdk
run: bun run typecheck

- name: Test
if: needs.changes.outputs.web == 'true'
working-directory: apps/web
run: bun run test

- name: Test widget SDK
if: needs.changes.outputs.web == 'true'
working-directory: packages/widget-sdk
run: bun run test

- name: Lint
if: needs.changes.outputs.lint == 'true'
run: bun x ultracite check
Expand Down
3 changes: 0 additions & 3 deletions apps/ios/ServerBee/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ struct ContentView: View {
_apiClient = State(initialValue: APIClient(authManager: authManager))
}

/// Test-only accessor — assert the client was built during init.
var apiClientForTest: APIClient { apiClient }

var body: some View {
ZStack(alignment: .top) {
TabView(selection: $selectedTab) {
Expand Down
17 changes: 0 additions & 17 deletions apps/ios/ServerBee/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -10287,23 +10287,6 @@
}
}
},
"Metrics History": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Metrics History"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "历史指标"
}
}
}
},
"Minor": {
"extractionState": "manual",
"localizations": {
Expand Down
7 changes: 1 addition & 6 deletions apps/ios/ServerBee/Models/AccountModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,7 @@ struct MobileDevice: Decodable, Identifiable, Sendable {
}
}

// MARK: - About

/// `GET /api/about` — server-reported version.
struct AboutInfo: Decodable, Sendable {
let version: String
}
// MARK: - Database status

/// Database (GeoIP / ASN) status (`GET /api/geoip/status`, `/api/asn/status`).
struct DbStatus: Decodable, Sendable {
Expand Down
11 changes: 0 additions & 11 deletions apps/ios/ServerBee/Models/SecurityModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,17 +227,6 @@ enum SecuritySeverity {
}
}

/// Higher = more severe, for sorting/emphasis.
static func rank(_ severity: String) -> Int {
switch severity {
case "critical": 4
case "high": 3
case "medium": 2
case "low": 1
default: 0
}
}

static func label(_ severity: String) -> String {
switch severity {
case "critical": String(localized: "Critical")
Expand Down
27 changes: 9 additions & 18 deletions apps/ios/ServerBee/ViewModels/ServerDetailViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,13 @@ import SwiftUI
@MainActor
@Observable
final class ServerDetailViewModel {
var server: ServerStatus?
/// Full REST configuration (capabilities, billing, kernel, agent version,
/// enrollment state) — fields the live WebSocket frame never carries.
var config: ServerConfig?
var records: [MetricRecord] = []
var isLoading = false
var isLoadingConfig = false

/// Set the server from the parent list (avoids a separate network fetch).
func setServer(_ server: ServerStatus) {
self.server = server
}

/// Fetch the server detail individually if not already provided.
func fetchDetail(serverId: String, apiClient: APIClient) async {
guard server == nil else { return }
do {
server = try await apiClient.get("/api/servers/\(serverId)")
} catch {
AppLog.viewModel.error("ServerDetail fetch failed: \(String(describing: error), privacy: .public)")
}
}
private var recordsFetchGeneration = 0

/// Fetch the authoritative REST configuration for the detail screen.
func fetchConfig(serverId: String, apiClient: APIClient) async {
Expand All @@ -41,11 +26,17 @@ final class ServerDetailViewModel {

/// Fetch historical metric records for the given server and time range.
func fetchRecords(serverId: String, range: String, apiClient: APIClient) async {
guard !Task.isCancelled else { return }
recordsFetchGeneration += 1
let generation = recordsFetchGeneration
isLoading = true
defer { isLoading = false }
defer { if generation == recordsFetchGeneration { isLoading = false } }
do {
records = try await apiClient.get(MetricsHistoryQuery(range: range).path(serverId: serverId))
let refreshed: [MetricRecord] = try await apiClient.get(MetricsHistoryQuery(range: range).path(serverId: serverId))
guard generation == recordsFetchGeneration, !Task.isCancelled else { return }
records = refreshed
} catch {
guard generation == recordsFetchGeneration, !Task.isCancelled else { return }
AppLog.viewModel.error("ServerDetail records fetch failed: \(String(describing: error), privacy: .public)")
}
}
Expand Down
29 changes: 5 additions & 24 deletions apps/ios/ServerBee/Views/Servers/MetricsHistoryView.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import SwiftUI

/// Range selector + history charts for a server. Reusable content (no
/// navigation chrome) so it can back both the standalone screen and the
/// detail "Metrics" tab. Consumes `APIClient` from the environment.
/// Range selector + history charts for the server detail's Metrics tab.
/// Consumes `APIClient` from the environment.
struct MetricsContentView: View {
let serverId: String

Expand All @@ -21,7 +20,7 @@ struct MetricsContentView: View {
.padding()
}
.background(Color(.systemGroupedBackground))
.task {
.task(id: selectedRange) {
await viewModel.fetchRecords(serverId: serverId, range: selectedRange, apiClient: apiClient)
}
.task {
Expand All @@ -32,11 +31,6 @@ struct MetricsContentView: View {
await viewModel.fetchConfig(serverId: serverId, apiClient: apiClient)
}
}
.onChange(of: selectedRange) { _, newRange in
Task {
await viewModel.fetchRecords(serverId: serverId, range: newRange, apiClient: apiClient)
}
}
}

private var timeRangeSelector: some View {
Expand Down Expand Up @@ -111,24 +105,11 @@ struct MetricsContentView: View {
}
}

/// Standalone history screen (still reachable as a pushed destination).
struct MetricsHistoryView: View {
let serverId: String

var body: some View {
MetricsContentView(serverId: serverId)
.navigationTitle(String(localized: "Metrics History"))
.navigationBarTitleDisplayMode(.inline)
}
}

// MARK: - Metrics history

/// Renders the standard set of history charts from raw metric records:
/// CPU, Memory, Disk, Network, Disk I/O (when present), Load (when present)
/// and Temperature (when reported). Pure presentation — owns no fetching,
/// scroll, or navigation chrome so it can be embedded in both the standalone
/// history screen and the detail tab.
/// and Temperature (when reported). Owns no fetching, scroll, or navigation chrome.
struct MetricsCharts: View {
let records: [MetricRecord]
/// Installed memory / disk capacity from the server configuration. History
Expand Down Expand Up @@ -381,6 +362,6 @@ enum MetricsSummary {

#Preview {
NavigationStack {
MetricsHistoryView(serverId: "1")
MetricsContentView(serverId: "1")
}
}
6 changes: 1 addition & 5 deletions apps/ios/ServerBeeTests/AccountModelsDecodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,7 @@ final class AccountModelsDecodingTests: XCTestCase {
XCTAssertTrue(json.contains("\"new_password\":\"bbbbbbbb\""))
}

// MARK: - About / DB status

func test_aboutInfo_decodes() throws {
XCTAssertEqual(try decode(AboutInfo.self, #"{"version":"1.2.3"}"#).version, "1.2.3")
}
// MARK: - DB status

func test_dbStatus_decodesInstalled() throws {
let json = """
Expand Down
13 changes: 0 additions & 13 deletions apps/ios/ServerBeeTests/ContentViewInitTests.swift

This file was deleted.

4 changes: 2 additions & 2 deletions apps/ios/ServerBeeTests/MetricsHistoryViewInitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ final class MetricsHistoryViewInitTests: XCTestCase {
)
XCTAssertFalse(
source.contains("String(localized: \"Language\")"),
"Appearance must only expose Theme; language selection belongs to iOS Settings"
"Appearance must only expose Theme; language selection has its own Settings screen"
)
}

Expand All @@ -48,7 +48,7 @@ final class MetricsHistoryViewInitTests: XCTestCase {
)
XCTAssertFalse(
source.contains("APIClient(authManager:"),
"MetricsHistoryView must consume APIClient from the environment, not construct one"
"MetricsContentView must consume APIClient from the environment, not construct one"
)
}
}
14 changes: 0 additions & 14 deletions apps/ios/ServerBeeTests/RefreshCoordinatorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,6 @@ import XCTest

@MainActor
final class RefreshCoordinatorTests: XCTestCase {
override func setUp() async throws {
URLProtocol.registerClass(URLProtocolStub.self)
URLProtocolStub.stubResponse = nil
URLProtocolStub.stubError = nil
URLProtocolStub.stubResponseFactory = nil
}

override func tearDown() async throws {
URLProtocol.unregisterClass(URLProtocolStub.self)
URLProtocolStub.stubResponse = nil
URLProtocolStub.stubError = nil
URLProtocolStub.stubResponseFactory = nil
}

/// Concurrent successful refresh attempts coalesce: every caller observes
/// the same token without firing the underlying `refreshFn` more than once.
///
Expand Down
15 changes: 0 additions & 15 deletions apps/ios/ServerBeeTests/SmokeTests.swift

This file was deleted.

Loading
Loading