Skip to content
Open
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
Binary file added assets/speed_test_indicator_img/coverage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1,527 changes: 1,527 additions & 0 deletions docs/IPERF3_IMPLEMENTATION.md

Large diffs are not rendered by default.

Binary file added flutter_01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 0 additions & 11 deletions lib/core/config/environment.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'package:flutter/foundation.dart';

/// Environment configuration for different build flavors
enum Environment { development, staging, production }

Expand All @@ -11,15 +9,6 @@ class EnvironmentConfig {

static void setEnvironment(Environment env) {
_environment = env;
// Only log in debug mode to avoid memory issues
if (kDebugMode) {
debugPrint(
'EnvironmentConfig: Environment set, isDevelopment=$isDevelopment, '
'isStaging=$isStaging, isProduction=$isProduction',
);
debugPrint('EnvironmentConfig: WebSocket URL will be: $websocketBaseUrl');
debugPrint('EnvironmentConfig: useSyntheticData=$useSyntheticData');
}
}

static Environment get environment => _environment;
Expand Down
10 changes: 10 additions & 0 deletions lib/core/providers/websocket_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:rgnets_fdk/core/config/environment.dart';
import 'package:rgnets_fdk/core/config/logger_config.dart';
import 'package:rgnets_fdk/core/providers/core_providers.dart';
import 'package:rgnets_fdk/core/providers/repository_providers.dart';
import 'package:rgnets_fdk/core/services/ap_uplink_service.dart';
import 'package:rgnets_fdk/core/services/cache_manager.dart';
import 'package:rgnets_fdk/core/services/websocket_cache_integration.dart';
import 'package:rgnets_fdk/core/services/websocket_data_sync_service.dart';
Expand Down Expand Up @@ -162,6 +163,15 @@ final webSocketCacheIntegrationProvider = Provider<WebSocketCacheIntegration>((
return integration;
});

/// Provides AP uplink info via cached lookups (fetching as needed).
final apUplinkInfoProvider = FutureProvider.family<APUplinkInfo?, int>((
ref,
apId,
) {
final cacheIntegration = ref.watch(webSocketCacheIntegrationProvider);
return cacheIntegration.getAPUplinkInfo(apId);
});

/// Emits the last device-cache update time for WebSocket snapshots/updates.
final webSocketDeviceLastUpdateProvider = StreamProvider<DateTime?>((ref) {
final integration = ref.watch(webSocketCacheIntegrationProvider);
Expand Down
154 changes: 154 additions & 0 deletions lib/core/services/ap_uplink_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import 'package:logger/logger.dart';
import 'package:rgnets_fdk/core/services/websocket_service.dart';

class APUplinkInfo {
const APUplinkInfo({
required this.apId,
this.linkSpeed,
this.speedInBps,
this.portName,
this.portNumber,
this.rawPortData,
});

final int apId;
final int? linkSpeed;
final int? speedInBps;
final String? portName;
final int? portNumber;
final Map<String, dynamic>? rawPortData;
}

class APUplinkService {
APUplinkService({
required WebSocketService webSocketService,
Logger? logger,
Map<int, APUplinkInfo>? cache,
}) : _webSocketService = webSocketService,
_logger = logger ?? Logger(),
_cache = cache ?? <int, APUplinkInfo>{};

final WebSocketService _webSocketService;
final Logger _logger;
final Map<int, APUplinkInfo> _cache;
final Map<int, Future<APUplinkInfo?>> _inFlight = {};

Map<int, APUplinkInfo> get cache => _cache;

APUplinkInfo? getCachedUplink(int apId) {
return _cache[apId];
}

Future<APUplinkInfo?> getAPUplinkPortDetail(int apId) {
final cached = _cache[apId];
if (cached != null) {
return Future.value(cached);
}

final inflight = _inFlight[apId];
if (inflight != null) {
return inflight;
}

final request = fetchAPUplinkDetail(apId);
_inFlight[apId] = request;
return request.whenComplete(() => _inFlight.remove(apId));
}

Future<APUplinkInfo?> fetchAPUplinkDetail(int apId) async {
if (!_webSocketService.isConnected) {
_logger.w(
'APUplinkService: WebSocket disconnected, cannot fetch uplink for AP $apId',
);
return null;
}

try {
final apResponse = await _webSocketService.requestActionCable(
action: 'resource_action',
resourceType: 'access_points',
additionalData: {'crud_action': 'show', 'id': apId},
timeout: const Duration(seconds: 15),
);

final infrastructureLinkId = _parseInt(
apResponse.payload['data']?['infrastructure_link_id'],
);
if (infrastructureLinkId == null) {
_logger.i(
'APUplinkService: No infrastructure_link_id found for AP $apId',
);
return null;
}

final linkResponse = await _webSocketService.requestActionCable(
action: 'resource_action',
resourceType: 'infrastructure_links',
additionalData: {'crud_action': 'show', 'id': infrastructureLinkId},
timeout: const Duration(seconds: 15),
);

final switchPorts = linkResponse.payload['data']?['switch_ports'];
if (switchPorts is! List || switchPorts.isEmpty) {
_logger.i(
'APUplinkService: No switch_ports found for infrastructure link $infrastructureLinkId',
);
return null;
}

final firstPort = switchPorts.first;
final portId = firstPort is Map<String, dynamic>
? _parseInt(firstPort['id'])
: _parseInt(firstPort);
if (portId == null) {
_logger.w(
'APUplinkService: Could not determine switch port id for AP $apId',
);
return null;
}

final portResponse = await _webSocketService.requestActionCable(
action: 'resource_action',
resourceType: 'switch_ports',
additionalData: {'crud_action': 'show', 'id': portId},
timeout: const Duration(seconds: 15),
);

final portData = portResponse.payload['data'];
if (portData is! Map<String, dynamic>) {
_logger.w(
'APUplinkService: Invalid switch_port payload for port $portId',
);
return null;
}

final info = APUplinkInfo(
apId: apId,
linkSpeed: _parseInt(portData['link_speed']),
speedInBps: _parseInt(portData['speed_in_bps']),
portName: portData['name']?.toString(),
portNumber: _parseInt(portData['port']),
rawPortData: portData,
);

_cache[apId] = info;
return info;
} catch (e) {
_logger.e('APUplinkService: Failed to fetch uplink for AP $apId: $e');
return null;
}
}

void clearCache() {
_cache.clear();
_inFlight.clear();
}

int? _parseInt(dynamic value) {
if (value == null) return null;
if (value is int) return value;
if (value is double) return value.toInt();
if (value is String) return int.tryParse(value);
return int.tryParse(value.toString());
}
}
Loading