feat: écran détail serveur (stats CPU/RAM + image importable RustFS)
Build APK / build (push) Successful in 2m36s

- carte cliquable → ServerDetailScreen : bannière (image ou thème), statut,
  connexion/ouvrir, joueurs/MOTD, jauges CPU/RAM/réseau (docker stats),
  import/suppression d'image (galerie → backend → RustFS).
- api_service: stats(), server(), imageBytes(), uploadImage(), deleteImage().
- deps: image_picker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
neckfire
2026-07-13 12:05:09 +02:00
co-authored by Claude Opus 4.8
parent a971204b80
commit 2862fdd408
7 changed files with 504 additions and 2 deletions
+45
View File
@@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
@@ -36,6 +37,14 @@ class ApiService {
return list.map((e) => GameServer.fromJson(e as Map<String, dynamic>)).toList();
}
Future<GameServer> server(String id) async {
final base = await baseUrl;
final res = await http.get(Uri.parse('$base/api/servers/$id'), headers: await _headers());
if (res.statusCode == 401) throw UnauthorizedException();
if (res.statusCode != 200) throw ApiException('Erreur ${res.statusCode}');
return GameServer.fromJson(jsonDecode(res.body) as Map<String, dynamic>);
}
Future<void> start(String id) => _action(id, 'start');
Future<void> stop(String id) => _action(id, 'stop');
@@ -50,6 +59,42 @@ class ApiService {
}
Future<String> iconUrl(String id) async => '${await baseUrl}/api/servers/$id/icon';
/// Stats d'utilisation du conteneur (CPU/RAM/réseau). `{running:false}` si éteint.
Future<Map<String, dynamic>> stats(String id) async {
final base = await baseUrl;
final res = await http.get(Uri.parse('$base/api/servers/$id/stats'), headers: await _headers());
if (res.statusCode == 401) throw UnauthorizedException();
if (res.statusCode != 200) throw ApiException('Erreur ${res.statusCode}');
return jsonDecode(res.body) as Map<String, dynamic>;
}
/// Image importée du serveur (bytes), null si aucune.
Future<Uint8List?> imageBytes(String id) async {
final base = await baseUrl;
final res = await http.get(Uri.parse('$base/api/servers/$id/image'), headers: await _headers());
if (res.statusCode == 401) throw UnauthorizedException();
if (res.statusCode != 200) return null;
return res.bodyBytes;
}
Future<void> uploadImage(String id, String filePath) async {
final base = await baseUrl;
final token = await _auth.validToken();
final req = http.MultipartRequest('POST', Uri.parse('$base/api/servers/$id/image'))
..headers['Authorization'] = 'Bearer ${token ?? ''}'
..files.add(await http.MultipartFile.fromPath('file', filePath));
final res = await http.Response.fromStream(await req.send());
if (res.statusCode == 401) throw UnauthorizedException();
if (res.statusCode >= 400) throw ApiException('Envoi refusé (${res.statusCode})');
}
Future<void> deleteImage(String id) async {
final base = await baseUrl;
final res = await http.delete(Uri.parse('$base/api/servers/$id/image'), headers: await _headers());
if (res.statusCode == 401) throw UnauthorizedException();
if (res.statusCode >= 400) throw ApiException('Suppression refusée (${res.statusCode})');
}
}
class UnauthorizedException implements Exception {}