3cb980b629
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>
249 lines
7.7 KiB
Dart
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));
|