Files
tasq/lib/providers/network_map/network_devices_provider.dart
T
redz1029 3cb980b629 QA hardening: security RLS fixes, Flutter 3.47.5 upgrade, UI/validation fixes
Security — enforce write authorization server-side (was UI/RPC-only):
- it_service_requests RLS: block cross-office read/edit + self-approve (QA-015)
- pass_slips RLS: owner can complete but not self-approve (QA-046)
- swap_requests RLS: scope select/update to participants + admin (QA-047)
- storage: tighten it_service_attachments + task_attachments write/delete (QA-027)
- admin_user_management edge function: allow programmers to manage users (QA-016)

Fixes:
- workforce generator "uncovered shifts" false alarms (QA-043/044)
- network-map VLAN + New-location dialog validation, disabled-until-valid (QA-048)
- de-flake time-of-day-dependent dashboard metrics test (QA-045)

Toolchain:
- upgrade to Flutter 3.47.5 / Dart 3.13.4; font_awesome_flutter 11.0.0,
  flutter_quill 11.6.0, pdfrx 2.6.5; clear resulting deprecations (QA-002)

analyze clean; 139 tests pass; web build succeeds. Report + evidence in docs/qa/.

Note: also carries the in-progress Brick model cleanup already present in the
working tree. QA-001 (AI keys public in the build) is deferred by owner decision.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-09-26 11:52:16 +08:00

249 lines
7.7 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../models/network/network_device.dart';
import '../../models/network/network_link.dart';
import '../../models/network/network_port.dart';
import '../../models/network/network_vlan.dart';
import '../supabase_provider.dart';
final networkDevicesProvider = FutureProvider<List<NetworkDevice>>((ref) async {
final client = ref.watch(supabaseClientProvider);
final rows = await client.from('network_devices').select().order('name', ascending: true);
return (rows as List)
.map((r) => NetworkDevice.fromMap(r as Map<String, dynamic>))
.toList();
});
final networkPortsProvider = FutureProvider<List<NetworkPort>>((ref) async {
final client = ref.watch(supabaseClientProvider);
final rows = await client.from('network_ports').select().order('port_number', ascending: true);
return (rows as List)
.map((r) => NetworkPort.fromMap(r as Map<String, dynamic>))
.toList();
});
final networkLinksProvider = FutureProvider<List<NetworkLink>>((ref) async {
final client = ref.watch(supabaseClientProvider);
final rows = await client.from('network_links').select();
return (rows as List)
.map((r) => NetworkLink.fromMap(r as Map<String, dynamic>))
.toList();
});
final networkVlansProvider = FutureProvider<List<NetworkVlan>>((ref) async {
final client = ref.watch(supabaseClientProvider);
final rows = await client.from('network_vlans').select().order('vlan_id', ascending: true);
return (rows as List)
.map((r) => NetworkVlan.fromMap(r as Map<String, dynamic>))
.toList();
});
final networkDeviceByIdProvider =
FutureProvider.family<NetworkDevice?, String>((ref, id) async {
final client = ref.watch(supabaseClientProvider);
final row = await client
.from('network_devices')
.select()
.eq('id', id)
.maybeSingle();
if (row == null) return null;
return NetworkDevice.fromMap(row);
});
final networkPortsByDeviceProvider =
FutureProvider.family<List<NetworkPort>, String>((ref, deviceId) async {
final client = ref.watch(supabaseClientProvider);
final rows = await client
.from('network_ports')
.select()
.eq('device_id', deviceId)
.order('port_number', ascending: true);
return (rows as List)
.map((r) => NetworkPort.fromMap(r as Map<String, dynamic>))
.toList();
});
class NetworkDevicesController {
NetworkDevicesController(this.ref);
final Ref ref;
Future<NetworkDevice> createDevice({
required String name,
required NetworkDeviceKind kind,
NetworkDeviceRole? role,
String? vendor,
String? model,
String? serial,
String? mgmtIp,
String? mac,
String? locationId,
NetworkImportSource importSource = NetworkImportSource.manual,
NetworkDeviceStatus status = NetworkDeviceStatus.unknown,
String? notes,
}) async {
final client = ref.read(supabaseClientProvider);
final inserted = await client
.from('network_devices')
.insert({
'name': name,
'kind': kind.wire,
'role': ?role?.wire,
'vendor': ?vendor,
'model': ?model,
'serial': ?serial,
'mgmt_ip': ?mgmtIp,
'mac': ?mac,
'location_id': ?locationId,
'import_source': importSource.wire,
'status': status.wire,
'notes': ?notes,
})
.select()
.single();
ref.invalidate(networkDevicesProvider);
return NetworkDevice.fromMap(inserted);
}
Future<void> updateDevice(NetworkDevice device) async {
final client = ref.read(supabaseClientProvider);
await client
.from('network_devices')
.update(device.toUpdateMap())
.eq('id', device.id);
ref.invalidate(networkDevicesProvider);
ref.invalidate(networkDeviceByIdProvider(device.id));
}
Future<void> deleteDevice(String id) async {
final client = ref.read(supabaseClientProvider);
await client.from('network_devices').delete().eq('id', id);
ref.invalidate(networkDevicesProvider);
ref.invalidate(networkPortsProvider);
ref.invalidate(networkLinksProvider);
}
Future<NetworkPort> createPort({
required String deviceId,
required String portNumber,
NetworkPortKind? portKind,
int? speedMbps,
int? accessVlan,
bool isTrunk = false,
List<int> trunkVlans = const [],
String? notes,
}) async {
final client = ref.read(supabaseClientProvider);
final inserted = await client
.from('network_ports')
.insert({
'device_id': deviceId,
'port_number': portNumber,
'port_kind': ?portKind?.wire,
'speed_mbps': ?speedMbps,
'access_vlan': ?accessVlan,
'is_trunk': isTrunk,
if (trunkVlans.isNotEmpty) 'trunk_vlans': trunkVlans,
'notes': ?notes,
})
.select()
.single();
ref.invalidate(networkPortsProvider);
ref.invalidate(networkPortsByDeviceProvider(deviceId));
return NetworkPort.fromMap(inserted);
}
Future<void> deletePort(String id, {String? deviceId}) async {
final client = ref.read(supabaseClientProvider);
await client.from('network_ports').delete().eq('id', id);
ref.invalidate(networkPortsProvider);
ref.invalidate(networkLinksProvider);
if (deviceId != null) {
ref.invalidate(networkPortsByDeviceProvider(deviceId));
}
}
Future<void> updatePort({
required String id,
required String deviceId,
required String portNumber,
NetworkPortKind? portKind,
int? speedMbps,
int? accessVlan,
bool isTrunk = false,
List<int> trunkVlans = const [],
String? notes,
}) async {
final client = ref.read(supabaseClientProvider);
await client.from('network_ports').update({
'port_number': portNumber,
'port_kind': portKind?.wire,
'speed_mbps': speedMbps,
'access_vlan': accessVlan,
'is_trunk': isTrunk,
'trunk_vlans': trunkVlans.isEmpty ? null : trunkVlans,
'notes': notes,
}).eq('id', id);
ref.invalidate(networkPortsProvider);
ref.invalidate(networkPortsByDeviceProvider(deviceId));
}
Future<NetworkLink> createLink({
required String portA,
required String portB,
NetworkLinkKind? linkKind,
String? cableLabel,
String? notes,
}) async {
final client = ref.read(supabaseClientProvider);
final inserted = await client
.from('network_links')
.insert(NetworkLink.insertMapFor(
portA: portA,
portB: portB,
linkKind: linkKind,
cableLabel: cableLabel,
notes: notes,
))
.select()
.single();
ref.invalidate(networkLinksProvider);
return NetworkLink.fromMap(inserted);
}
Future<void> deleteLink(String id) async {
final client = ref.read(supabaseClientProvider);
await client.from('network_links').delete().eq('id', id);
ref.invalidate(networkLinksProvider);
}
Future<NetworkVlan> createVlan({
required int vlanId,
required String name,
String? description,
String? color,
}) async {
final client = ref.read(supabaseClientProvider);
final inserted = await client
.from('network_vlans')
.insert({
'vlan_id': vlanId,
'name': name,
'description': ?description,
'color': ?color,
})
.select()
.single();
ref.invalidate(networkVlansProvider);
return NetworkVlan.fromMap(inserted);
}
Future<void> deleteVlan(String id) async {
final client = ref.read(supabaseClientProvider);
await client.from('network_vlans').delete().eq('id', id);
ref.invalidate(networkVlansProvider);
}
}
final networkDevicesControllerProvider =
Provider<NetworkDevicesController>((ref) => NetworkDevicesController(ref));