- Add GeViScope Bridge (C# .NET 8.0) on port 7720 - Full SDK wrapper for camera control, PTZ, actions/events - 17 REST API endpoints for GeViScope server interaction - Support for MCS (Media Channel Simulator) with 16 test channels - Real-time action/event streaming via PLC callbacks - Add GeViServer Bridge (C# .NET 8.0) on port 7710 - Integration with GeViSoft orchestration layer - Input/output control and event management - Update Python API with new routers - /api/geviscope/* - Proxy to GeViScope Bridge - /api/geviserver/* - Proxy to GeViServer Bridge - /api/excel/* - Excel import functionality - Add Flutter app GeViScope integration - GeViScopeRemoteDataSource with 17 API methods - GeViScopeBloc for state management - GeViScopeScreen with PTZ controls - App drawer navigation to GeViScope - Add SDK documentation (extracted from PDFs) - GeViScope SDK docs (7 parts + action reference) - GeViSoft SDK docs (12 chunks) - Add .mcp.json for Claude Code MCP server config Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
162 lines
5.7 KiB
Dart
162 lines
5.7 KiB
Dart
import 'dart:typed_data';
|
|
import 'package:dio/dio.dart';
|
|
import '../../domain/entities/server.dart';
|
|
import '../../core/constants/api_constants.dart';
|
|
import '../../core/storage/token_manager.dart';
|
|
import '../data_sources/local/server_local_data_source.dart';
|
|
import '../models/server_hive_model.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
class ExcelImportService {
|
|
final _uuid = const Uuid();
|
|
final Dio _dio = Dio();
|
|
final ServerLocalDataSource? _localDataSource;
|
|
|
|
ExcelImportService({ServerLocalDataSource? localDataSource})
|
|
: _localDataSource = localDataSource;
|
|
|
|
/// Import servers from Excel file using backend API
|
|
/// Expected columns (starting from row 2):
|
|
/// - Column B: Hostname/Alias
|
|
/// - Column C: Type (GeViScope or G-Core)
|
|
/// - Column D: IP Server/Host
|
|
/// - Column E: Username
|
|
/// - Column F: Password
|
|
Future<List<Server>> importServersFromExcel(Uint8List fileBytes, String fileName) async {
|
|
try {
|
|
print('[ExcelImport] Starting import, file size: ${fileBytes.length} bytes');
|
|
|
|
// Get auth token
|
|
final token = TokenManager().accessToken;
|
|
|
|
// Prepare multipart request
|
|
final formData = FormData.fromMap({
|
|
'file': MultipartFile.fromBytes(
|
|
fileBytes,
|
|
filename: fileName,
|
|
),
|
|
});
|
|
|
|
// Call backend API
|
|
final response = await _dio.post(
|
|
'${ApiConstants.baseUrl}/excel/import-servers',
|
|
data: formData,
|
|
options: Options(
|
|
headers: {
|
|
'Authorization': 'Bearer $token',
|
|
},
|
|
),
|
|
);
|
|
|
|
if (response.statusCode != 200) {
|
|
throw Exception('Server returned status ${response.statusCode}');
|
|
}
|
|
|
|
final data = response.data as Map<String, dynamic>;
|
|
final serversData = data['servers'] as List<dynamic>;
|
|
|
|
print('[ExcelImport] Server returned ${serversData.length} servers');
|
|
|
|
// Convert API response to Server entities
|
|
final servers = <Server>[];
|
|
for (final serverData in serversData) {
|
|
final serverType = serverData['type'] == 'gcore'
|
|
? ServerType.gcore
|
|
: ServerType.geviscope;
|
|
|
|
final server = Server(
|
|
id: _uuid.v4(),
|
|
alias: serverData['alias'] as String,
|
|
host: serverData['host'] as String,
|
|
user: serverData['user'] as String? ?? 'sysadmin',
|
|
password: serverData['password'] as String? ?? '',
|
|
type: serverType,
|
|
enabled: serverData['enabled'] as bool? ?? true,
|
|
deactivateEcho: serverData['deactivateEcho'] as bool? ?? false,
|
|
deactivateLiveCheck: serverData['deactivateLiveCheck'] as bool? ?? false,
|
|
);
|
|
|
|
servers.add(server);
|
|
}
|
|
|
|
print('[ExcelImport] Import completed: ${servers.length} servers parsed');
|
|
return servers;
|
|
} catch (e) {
|
|
print('[ExcelImport] Fatal error: $e');
|
|
if (e is DioException) {
|
|
print('[ExcelImport] DioException type: ${e.type}');
|
|
print('[ExcelImport] Response status: ${e.response?.statusCode}');
|
|
print('[ExcelImport] Response data: ${e.response?.data}');
|
|
print('[ExcelImport] Request URL: ${e.requestOptions.uri}');
|
|
|
|
final errorMessage = e.response?.data?['detail'] ??
|
|
e.response?.data?['error'] ??
|
|
e.message ??
|
|
'Unknown error';
|
|
throw Exception('Failed to import Excel file: $errorMessage');
|
|
}
|
|
throw Exception('Failed to import Excel file: $e');
|
|
}
|
|
}
|
|
|
|
/// Merge imported servers with existing servers
|
|
/// Only adds servers that don't already exist (based on alias or host)
|
|
List<Server> mergeServers({
|
|
required List<Server> existing,
|
|
required List<Server> imported,
|
|
}) {
|
|
final newServers = <Server>[];
|
|
int duplicateCount = 0;
|
|
|
|
for (final importedServer in imported) {
|
|
// Check if server already exists by alias or host
|
|
final isDuplicate = existing.any((existingServer) =>
|
|
existingServer.alias.toLowerCase() == importedServer.alias.toLowerCase() ||
|
|
existingServer.host.toLowerCase() == importedServer.host.toLowerCase());
|
|
|
|
if (!isDuplicate) {
|
|
newServers.add(importedServer);
|
|
print('[ExcelImport] New server: ${importedServer.alias}');
|
|
} else {
|
|
duplicateCount++;
|
|
print('[ExcelImport] Duplicate skipped: ${importedServer.alias}');
|
|
}
|
|
}
|
|
|
|
print('[ExcelImport] Merge complete: ${newServers.length} new servers, $duplicateCount duplicates skipped');
|
|
return newServers;
|
|
}
|
|
|
|
/// Save imported servers directly to local storage as dirty (unsaved) servers
|
|
/// This bypasses the bloc to avoid triggering multiple rebuilds during import
|
|
Future<void> saveImportedServersToStorage(List<Server> servers) async {
|
|
if (_localDataSource == null) {
|
|
throw Exception('LocalDataSource not available for direct storage access');
|
|
}
|
|
|
|
print('[ExcelImport] Saving ${servers.length} servers directly to storage...');
|
|
|
|
for (final server in servers) {
|
|
final hiveModel = ServerHiveModel(
|
|
id: server.id,
|
|
alias: server.alias,
|
|
host: server.host,
|
|
user: server.user,
|
|
password: server.password,
|
|
serverType: server.type == ServerType.gcore ? 'gcore' : 'geviscope',
|
|
enabled: server.enabled,
|
|
deactivateEcho: server.deactivateEcho,
|
|
deactivateLiveCheck: server.deactivateLiveCheck,
|
|
isDirty: true, // Mark as dirty (unsaved change)
|
|
syncOperation: 'create', // Needs to be created on server
|
|
lastModified: DateTime.now(),
|
|
);
|
|
|
|
await _localDataSource!.saveServer(hiveModel);
|
|
print('[ExcelImport] Saved to storage: ${server.alias}');
|
|
}
|
|
|
|
print('[ExcelImport] All ${servers.length} servers saved to storage as unsaved changes');
|
|
}
|
|
}
|