Src/Content/Docs/Fr/Development/Architecture
---
title: Architecture
description: Modèles d'architecture et décisions de conception
---
Server Box suit les principes de la Clean Architecture avec une séparation claire entre les couches de données, de domaine et de présentation.
Architecture en couches
┌─────────────────────────────────────┐
│ Couche Présentation │
│ (lib/view/page/) │
│ - Pages, Widgets, Contrôleurs │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Couche Logique Métier │
│ (lib/data/provider/) │
│ - Providers Riverpod │
│ - Gestion de l'état │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Couche Données │
│ (lib/data/model/, store/) │
│ - Modèles, Stockage, Services │
└─────────────────────────────────────┘Modèles clés
Gestion de l'état : Riverpod
- Génération de code : Utilise riverpod_generator pour des providers type-safe
- State Notifiers : Pour un état mutable avec une logique métier
- Async Notifiers : Pour les états de chargement et d'erreur
- Stream Providers : Pour les données en temps réel
Modèles immuables : Freezed
- Tous les modèles de données utilisent Freezed pour l'immuabilité
- Types Union pour la représentation de l'état
- Sérialisation JSON intégrée
- Extensions CopyWith pour les mises à jour
Stockage local : Hive
- hive_ce : Édition communautaire de Hive
- Suivez le modèle existant : la plupart des stores utilisent hive_ce, tandis que certains modèles suivis déclarent encore explicitement @HiveType et @HiveField
- Adaptateurs de type auto-générés
- Stockage clé-valeur persistant
Injection de dépendances
Les services et les stores sont injectés via :
1. Providers : Exposer les dépendances à l'UI
2. GetIt : Localisation de services (le cas échéant)
3. Injection par constructeur : Dépendances explicites
Flux de données
Action Utilisateur → Widget → Provider → Service/Store → Mise à jour Modèle → Reconstruction UI1. L'utilisateur interagit avec le widget
2. Le widget appelle une méthode du provider
3. Le provider met à jour l'état via le service/store
4. Le changement d'état déclenche la reconstruction de l'UI
5. Le nouvel état est reflété dans le widget
Dépendances personnalisées
Le projet utilise plusieurs forks personnalisés pour étendre les fonctionnalités :
- dartssh2 : Fonctionnalités SSH améliorées
- xterm : Émulateur de terminal avec support mobile
- fl_lib : Composants UI et utilitaires partagés
Threading (Multi-processus)
- Isolates : Calculs lourds hors du thread principal
- paquet computer : Utilitaires multi-threading
- Async/Await : Opérations d'E/S non bloquantes
---
Src/Content/Docs/Fr/Development/Building
---
title: Construction (Building)
description: Instructions de construction pour différentes plateformes
---
Server Box utilise un système de construction personnalisé (fl_build) pour les constructions multiplateformes.
Prérequis
- Flutter SDK (canal stable)
- Outils spécifiques à la plateforme (Xcode pour iOS, Android Studio pour Android)
- Chaîne d'outils Rust (pour certaines dépendances natives)
Construction pour le développement
Exécuter en mode développement
flutter runExécuter sur un appareil spécifique
flutter run -d <id-appareil>Construction pour la production
Le projet utilise fl_build pour la construction :
Construire pour une plateforme spécifique
dart run fl_build -p <plateforme>Plateformes disponibles :
- ios
- android
- macos
- linux
- windows
Constructions spécifiques aux plateformes
iOS
dart run fl_build -p iosNécessite :
- macOS avec Xcode
- CocoaPods
- Compte Apple Developer pour la signature
Android
dart run fl_build -p androidNécessite :
- Android SDK
- Java Development Kit
- Keystore pour la signature
macOS
dart run fl_build -p macosLinux
dart run fl_build -p linuxWindows
dart run fl_build -p windowsNécessite Windows avec Visual Studio.
Pré/Post Construction
Le script make.dart gère :
- La génération des métadonnées
- Les mises à jour de la chaîne de version
- Les configurations spécifiques aux plateformes
Dépannage
Nettoyage de la construction (Clean Build)
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub getIncompatibilité de version
Assurez-vous que toutes les dépendances sont compatibles :
flutter pub upgradeListe de contrôle de publication (Release Checklist)
1. Mettre à jour la version dans pubspec.yaml
2. Exécuter la génération de code
3. Exécuter les tests
4. Construire pour toutes les plateformes cibles
5. Tester sur des appareils physiques
6. Créer une version (release) GitHub
---
Src/Content/Docs/Fr/Development/Codegen
---
title: Génération de code
description: Utiliser build_runner pour la génération de code
---
Server Box utilise intensivement la génération de code pour les modèles, la gestion de l'état et la sérialisation.
Quand exécuter la génération de code
À exécuter après avoir modifié :
- Des modèles avec l'annotation @freezed
- Des classes avec @JsonSerializable
- Des modèles Hive
- Des providers avec @riverpod
- Des localisations (fichiers ARB)
Exécuter la génération de code
Générer tout le code
dart run build_runner build --delete-conflicting-outputsNettoyer le cache de génération
dart run build_runner cleanPuis régénérer
dart run build_runner build --delete-conflicting-outputsFichiers générés
Freezed (.freezed.dart)
Modèles de données immuables avec types Union :
@freezed
class ServerState with _$ServerState {
const factory ServerState.connected() = Connected;
const factory ServerState.disconnected() = Disconnected;
const factory ServerState.error(String message) = Error;
}Sérialisation JSON (.g.dart)
Généré à partir de json_serializable :
@JsonSerializable()
class Server {
final String id;
final String name;
final String host; Server({required this.id, required this.name, required this.host});
factory Server.fromJson(Map<String, dynamic> json) =>
_$ServerFromJson(json);
Map<String, dynamic> toJson() => _$ServerToJson(this);
}
Providers Riverpod (.g.dart)
Généré à partir de l'annotation @riverpod :
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() => 0;
}Adaptateurs Hive (.g.dart)
Auto-générés pour les modèles Hive (hive_ce) :
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}Génération de localisation
flutter gen-l10nGénère lib/generated/l10n/ à partir des fichiers lib/l10n/*.arb.
Conseils
- Utilisez --delete-conflicting-outputs pour éviter les conflits
- Conservez les fichiers générés dans le contrôle de version lorsqu'ils sont déjà suivis par ce dépôt
- Ne modifiez jamais manuellement les fichiers générés
---
Src/Content/Docs/Fr/Development/State
---
title: Gestion de l'état
description: Modèles de gestion de l'état basés sur Riverpod
---
Server Box utilise Riverpod avec la génération de code pour la gestion de l'état.
Types de Provider
StateProvider
État simple qui peut être lu et écrit :
@riverpod
class Settings extends _$Settings {
@override
SettingsModel build() {
return SettingsModel.defaults();
} void update(SettingsModel newSettings) {
state = newSettings;
}
}
AsyncNotifierProvider
État qui se charge de manière asynchrone avec des états de chargement/erreur :
@riverpod
class ServerStatus extends _$ServerStatus {
@override
Future<StatusModel> build(Server server) async {
return fetchStatus(server);
} Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => fetchStatus(server));
}
}
StreamProvider
Données en temps réel provenant de flux (streams) :
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}Modèles d'état
États de chargement
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)Family Providers
Providers paramétrés :
@riverpod
List<Container> containers(ContainersRef ref, Server server) {
return containerService.list(server);
}Auto-Dispose
Providers qui se détruisent lorsqu'ils ne sont plus référencés :
@Riverpod(keepAlive: false)
class TempState extends _$TempState {
// ...
}Bonnes pratiques
1. Utiliser la génération de code : Utilisez toujours l'annotation @riverpod
2. Co-localiser les providers : Placez-les près des widgets qui les consomment
3. Éviter les singletons : Utilisez des providers à la place
4. Couches correctes : Gardez la logique UI séparée de la logique métier
Lire l'état dans les Widgets
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}Modifier l'état
ref.read(settingsProvider.notifier).update(newSettings);---
Src/Content/Docs/Fr/Development/Structure
---
title: Structure du projet
description: Comprendre la base de code de Server Box
---
Le projet Server Box suit une architecture modulaire avec une séparation claire des préoccupations.
Structure des répertoires
lib/
├── core/ # Utilitaires de base et extensions
├── data/ # Couche de données
│ ├── model/ # Modèles de données par fonctionnalité
│ ├── provider/ # Providers Riverpod
│ └── store/ # Stockage local (Hive)
├── view/ # Couche UI
│ ├── page/ # Pages principales
│ └── widget/ # Widgets réutilisables
├── generated/ # Localisation générée
├── l10n/ # Fichiers ARB de localisation
└── hive/ # Adaptateurs HiveCouche Core (lib/core/)
Contient les utilitaires, les extensions et la configuration du routage :
- Extensions : Extensions Dart pour les types courants
- Routes : Configuration du routage de l'application
- Utils : Fonctions utilitaires partagées
Couche Données (lib/data/)
Modèles (lib/data/model/)
Organisés par fonctionnalité :
- server/ - Modèles de connexion et d'état du serveur
- container/ - Modèles de conteneurs Docker
- ssh/ - Modèles de session SSH
- sftp/ - Modèles de fichiers SFTP
- app/ - Modèles spécifiques à l'application
Providers (lib/data/provider/)
Providers Riverpod pour l'injection de dépendances et la gestion de l'état :
- Providers de serveur
- Providers d'état de l'UI
- Providers de service
Stores (lib/data/store/)
Stockage local basé sur Hive :
- Stockage des serveurs
- Stockage des paramètres
- Stockage du cache
Couche Vue (lib/view/)
Pages (lib/view/page/)
Écrans principaux de l'application :
- server/ - Pages de gestion des serveurs
- ssh/ - Pages de terminal SSH
- container/ - Pages de conteneurs
- setting/ - Pages de paramètres
- storage/ - Pages SFTP
- snippet/ - Pages d'extraits de code (snippets)
Widgets (lib/view/widget/)
Composants UI réutilisables :
- Cartes de serveur
- Graphiques d'état
- Composants de saisie (input)
- Dialogues
Fichiers générés
- lib/generated/l10n/ - Localisation auto-générée
- *.g.dart - Code généré (json_serializable, freezed, hive, riverpod)
- *.freezed.dart - Classes immuables Freezed
Répertoire Packages (/packages/)
Contient les forks personnalisés des dépendances :
- dartssh2/ - Bibliothèque SSH
- xterm/ - Émulateur de terminal
- fl_lib/ - Utilitaires partagés
- fl_build/ - Système de construction
---
Src/Content/Docs/Fr/Development/Testing
---
title: Tests
description: Stratégies de test et exécution des tests
---
Exécuter les tests
Exécuter tous les tests
flutter testExécuter un fichier de test spécifique
flutter test test/battery_test.dartExécuter avec couverture de code
flutter test --coverageStructure des tests
Les tests se trouvent dans le répertoire test/. La suite actuelle est principalement plate et regroupée par comportement de parseur, de modèle et d’utilitaire, par exemple cpu_test.dart, container_test.dart et ssh_config_test.dart.
Tests unitaires
Tester la logique métier et les modèles de données :
test('devrait calculer le pourcentage du CPU', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});Tests de widgets
Tester les composants UI :
testWidgets('ServerCard affiche le nom du serveur', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: ServerCard(server: testServer),
),
),
); expect(find.text('Test Server'), findsOneWidget);
});
Tests de providers
Tester les providers Riverpod :
test('serverStatusProvider retourne le statut', () async {
final container = ProviderContainer();
final status = await container.read(serverStatusProvider(testServer).future);
expect(status, isA<StatusModel>());
});Dépendances externes
Évitez les tests qui dépendent de vrais serveurs SSH. Les tests de parseurs, modèles et constructeurs de commandes doivent rester déterministes ; ajoutez des fakes ou fixtures ciblés lorsqu’une fonctionnalité introduit une frontière de service.
Tests d'intégration
Le dépôt actuel ne contient pas de suite integration_test/. Ajoutez des tests d’intégration seulement lorsqu’une fonctionnalité nécessite une couverture end-to-end sur appareil ou flux applicatif complet.dart
testWidgets('flux d\'ajout de serveur', (tester) async {
await tester.pumpWidget(MyApp());
// Appuyer sur le bouton d'ajout
await tester.tap(find.byIcon(Icons.add));
await tester.pumpAndSettle();
// Remplir le formulaire
await tester.enterText(find.byKey(Key('name')), 'Test Server');
// ...
});
Bonnes pratiques
1. Arrange-Act-Assert : Structurer les tests clairement
2. Noms descriptifs : Les noms de tests doivent décrire le comportement
3. Une assertion par test : Garder les tests focalisés
4. Mocker les dépendances externes : Ne pas dépendre de serveurs réels
5. Tester les cas limites : Listes vides, valeurs nulles, etc.
---
Src/Content/Docs/Fr/Advanced/Bulk Import
---
title: Importation massive de serveurs
description: Importer plusieurs serveurs à partir d'un fichier JSON
---
Importez plusieurs configurations de serveur en une seule fois à l'aide d'un fichier JSON.
Format JSON
:::danger[Avertissement de sécurité]
Ne stockez jamais de mots de passe en clair dans des fichiers ! Cet exemple JSON montre un champ de mot de passe à des fins de démonstration uniquement, mais vous devriez :
- Préférer les clés SSH (pubKeyId) au lieu de pwd - elles sont plus sûres
- Utiliser des gestionnaires de mots de passe ou des variables d'environnement si vous devez utiliser des mots de passe
- Supprimer le fichier immédiatement après l'importation - ne laissez pas traîner des identifiants
- Ajouter au .gitignore - ne validez jamais de fichiers d'identifiants dans le contrôle de version
:::
[
{
"name": "Mon serveur",
"ip": "example.com",
"port": 22,
"user": "root",
"pwd": "password",
"pubKeyId": "",
"tags": ["production"],
"autoConnect": false
}
]
Champs
| Champ | Requis | Description |
|-------|----------|-------------|
| name | Oui | Nom d'affichage |
| ip | Oui | Domaine ou adresse IP |
| port | Oui | Port SSH (généralement 22) |
| user | Oui | Nom d'utilisateur SSH |
| pwd | Non | Mot de passe (à éviter - utilisez plutôt des clés SSH) |
| pubKeyId | Non | ID de clé privée (à partir des clés privées - recommandé) |
| tags | Non | Tags d'organisation |
| autoConnect | Non | Connexion automatique au démarrage |
| id | Non | ID serveur stable ; les valeurs absentes ou vides sont générées à l'import |
Étapes d'importation
1. Créer un fichier JSON avec les configurations de serveur
2. Paramètres → Sauvegarde → Importation massive de serveurs
3. Sélectionnez votre fichier JSON
4. Confirmez l'importation
Exemple
[
{
"name": "Production",
"ip": "prod.example.com",
"port": 22,
"user": "admin",
"pubKeyId": "my-key",
"tags": ["production", "web"]
},
{
"name": "Développement",
"ip": "dev.example.com",
"port": 2222,
"user": "dev",
"pubKeyId": "dev-key",
"tags": ["development"]
}
]
Conseils
- Utilisez des clés SSH au lieu de mots de passe lorsque cela est possible
- Testez la connexion après l'importation
- Organisez avec des tags pour une gestion plus facile
- Supprimez le fichier JSON après l'importation
- Ne validez jamais de fichiers JSON contenant des identifiants dans le contrôle de version
---
Src/Content/Docs/Fr/Advanced/Custom Commands
---
title: Commandes personnalisées
description: Afficher la sortie des commandes personnalisées sur la page du serveur
---
Ajoutez des commandes shell personnalisées pour afficher leur sortie sur la page de détails du serveur.
Configuration
1. Paramètres du serveur → Commandes personnalisées
2. Entrez les commandes au format JSON
Format de base
{
"Nom d'affichage": "commande shell"
}
Exemple :{
"Mémoire": "free -h",
"Disque": "df -h",
"Uptime": "uptime"
}
Visualisation des résultats
Après la configuration, les commandes personnalisées apparaissent sur la page de détails du serveur et s'actualisent automatiquement.
Noms de commandes spéciaux
server_card_top_right
Affichage sur la carte du serveur de la page d'accueil (coin supérieur droit) :
{
"server_card_top_right": "votre-commande-ici"
}
Conseils
Utilisez des chemins absolus :
{"Mon script": "/usr/local/bin/mon-script.sh"}
Commandes avec pipe :{"Processus principal": "ps aux | sort -rk 3 | head -5"}
Formater la sortie :{"Charge CPU": "uptime | awk -F'load average:' '{print $2}'"}
Gardez les commandes rapides : Moins de 5 secondes pour une meilleure expérience.Limiter la sortie :
{"Logs": "tail -20 /var/log/syslog"}
Sécurité
Les commandes s'exécutent avec les permissions de l'utilisateur SSH. Évitez les commandes qui modifient l'état du système.
---
Src/Content/Docs/Fr/Advanced/Custom Logo
---
title: Logo de serveur personnalisé
description: Utiliser des images personnalisées pour les cartes de serveur
---
Affichez des logos personnalisés sur les cartes de serveur à l'aide d'URL d'images.
Configuration
1. Paramètres du serveur → Logo personnalisé
2. Entrez l'URL de l'image
Espaces réservés d'URL
{DIST} - Distribution Linux
Remplacé automatiquement par la distribution détectée :
https://example.com/{DIST}.png
Devient : debian.png, ubuntu.png, arch.png, etc.{BRIGHT} - Thème
Remplacé automatiquement par le thème actuel :
https://example.com/{BRIGHT}.png
Devient : light.png ou dark.pngCombiner les deux
https://example.com/{DIST}-{BRIGHT}.png
Devient : debian-light.png, ubuntu-dark.png, etc.Conseils
- Utilisez les formats PNG ou SVG
- Taille recommandée : 64x64 à 128x128 pixels
- Utilisez des URL HTTPS
- Gardez des tailles de fichiers réduites
Distributions supportées
debian, ubuntu, centos, fedora, opensuse, kali, alpine, arch, rocky, deepin, armbian, wrt
Liste complète : dist.dart
---
Src/Content/Docs/Fr/Advanced/Json Settings
---
title: Paramètres cachés (JSON)
description: Accéder aux paramètres avancés via l'éditeur JSON
---
Certains paramètres sont masqués de l'interface utilisateur mais accessibles via l'éditeur JSON.
Accès
Appuyez longuement sur Paramètres dans le menu latéral pour ouvrir l'éditeur JSON.
Paramètres cachés courants
timeOut
Délai d'attente de connexion en secondes.
{"timeOut": 10}
Type : entier | Par défaut : 5 | Plage : 1-60recordHistory
Enregistrer l'historique (chemins SFTP, etc.).
{"recordHistory": true}
Type : booléen | Par défaut : truetextFactor
Facteur de mise à l'échelle du texte.
{"textFactor": 1.2}
Type : double | Par défaut : 1.0 | Plage : 0.8-1.5Trouver plus de paramètres
Tous les paramètres sont définis dans setting.dart.
Recherchez :
late final settingName = StoreProperty(box, 'settingKey', defaultValue);
⚠️ Important
Avant d'éditer :
- Créer une sauvegarde - De mauvais paramètres peuvent empêcher l'ouverture de l'application
- Éditer avec soin - Le JSON doit être valide
Récupération
Si l'application ne s'ouvre plus après l'édition :
1. Effacer les données de l'application (dernier recours)
2. Réinstaller l'application
3. Restaurer à partir d'une sauvegarde
---
Src/Content/Docs/Fr/Advanced/Troubleshooting
---
title: Problèmes courants
description: Solutions aux problèmes fréquents
---
Problèmes de connexion
SSH ne se connecte pas
Symptômes : Délai d'attente (timeout), connexion refusée, échec d'authentification
Solutions :
1. Vérifier le type de serveur : Seuls les systèmes de type Unix sont supportés (Linux, macOS, Android/Termux)
2. Tester manuellement : ssh utilisateur@serveur -p port
3. Vérifier le pare-feu : Le port 22 doit être ouvert
4. Vérifier les identifiants : Nom d'utilisateur et mot de passe/clé corrects
Déconnexions fréquentes
Symptômes : Le terminal se déconnecte après une période d'inactivité
Solutions :
1. Keep-alive du serveur :
# /etc/ssh/sshd_config
ClientAliveInterval 60
ClientAliveCountMax 3
2. Désactiver l'optimisation de la batterie :
- MIUI : Batterie → "Pas de restrictions"
- Android : Paramètres → Applications → Désactiver l'optimisation
- iOS : Activer l'actualisation en arrière-planProblèmes de saisie
Impossible de taper certains caractères
Solution : Paramètres → Type de clavier → Passer à visiblePassword
Note : La saisie CJK (Chinois, Japonais, Coréen) peut ne pas fonctionner après ce changement.
Problèmes de l'application
L'application plante au démarrage
Symptômes : L'application ne s'ouvre pas, écran noir
Causes : Paramètres corrompus, particulièrement via l'éditeur JSON
Solutions :
1. Effacer les données de l'application :
- Android : Paramètres → Applications → ServerBox → Effacer les données
- iOS : Supprimer et réinstaller
2. Restaurer une sauvegarde : Importer une sauvegarde créée avant de modifier les paramètres
Problèmes de sauvegarde/restauration
La sauvegarde ne fonctionne pas :
- Vérifier l'espace de stockage
- Vérifier que l'application a les permissions de stockage
- Essayer un autre emplacement
La restauration échoue :
- Vérifier l'intégrité du fichier de sauvegarde
- Vérifier la compatibilité de la version de l'application
Problèmes de Widget
Le widget ne se met pas à jour
iOS :
- Attendre jusqu'à 30 minutes pour le rafraîchissement automatique
- Supprimer et rajouter le widget
- Vérifier que l'URL se termine par /status
Android :
- Appuyer sur le widget pour forcer le rafraîchissement
- Vérifier que l'ID du widget correspond à la configuration dans les paramètres de l'application
watchOS :
- Redémarrer l'application sur la montre
- Attendre quelques minutes après un changement de configuration
- Vérifier le format de l'URL
Le widget affiche une erreur
- Vérifier que ServerBox Monitor fonctionne sur le serveur
- Tester l'URL dans un navigateur
- Vérifier les identifiants d'authentification
Problèmes de performance
L'application est lente
Solutions :
- Réduire la fréquence de rafraîchissement dans les paramètres
- Vérifier la vitesse du réseau
- Désactiver les serveurs inutilisés
Utilisation élevée de la batterie
Solutions :
- Augmenter les intervalles de rafraîchissement
- Désactiver le rafraîchissement en arrière-plan
- Fermer les sessions SSH inutilisées
Obtenir de l'aide
Si les problèmes persistent :
1. Rechercher dans les Issues GitHub : https://github.com/lollipopkit/flutter_server_box/issues
2. Créer une nouvelle Issue : Inclure la version de l'application, la plateforme et les étapes pour reproduire le problème
3. Consulter le Wiki : Cette documentation et le Wiki GitHub
---
Src/Content/Docs/Fr/Advanced/Widgets
---
title: Widgets de l'écran d'accueil
description: Ajoutez des widgets d'état du serveur à votre écran d'accueil
---
Nécessite l'installation de ServerBox Monitor sur vos serveurs.
Prérequis
Installez d'abord ServerBox Monitor sur votre serveur. Consultez le Wiki de ServerBox Monitor pour les instructions de configuration.
Après l'installation, votre serveur doit avoir :
- Un point de terminaison HTTP/HTTPS
- Un point de terminaison API /status
- Une authentification facultative
Format de l'URL
https://votre-serveur.com/status
Doit se terminer par /status.Widget iOS
Configuration
1. Appuyez longuement sur l'écran d'accueil → Appuyez sur +
2. Recherchez "ServerBox"
3. Choisissez la taille du widget
4. Appuyez longuement sur le widget → Modifier le widget
5. Entrez l'URL se terminant par /status
Notes
- Doit utiliser HTTPS (sauf pour les adresses IP locales)
- Taux de rafraîchissement maximal : 30 minutes (limite iOS)
- Ajoutez plusieurs widgets pour plusieurs serveurs
Widget Android
Configuration
1. Appuyez longuement sur l'écran d'accueil → Widgets
2. Trouvez "ServerBox" → Ajoutez à l'écran d'accueil
3. Notez le numéro d'ID du widget affiché
4. Ouvrez l'application ServerBox → Paramètres
5. Appuyez sur Configurer le lien du widget d'accueil
6. Ajoutez l'entrée : Widget ID = URL d'état
Exemple :
- Clé : 17
- Valeur : https://mon-serveur.com/status
7. Appuyez sur le widget sur l'écran d'accueil pour le rafraîchir
Widget watchOS
Configuration
1. Ouvrez l'application iPhone → Paramètres
2. Paramètres iOS → Application Watch
3. Appuyez sur Ajouter une URL
4. Entrez l'URL se terminant par /status
5. Attendez que l'application de la montre se synchronise
Notes
- Essayez de redémarrer l'application de la montre si elle ne se met pas à jour
- Vérifiez que le téléphone et la montre sont connectés
Dépannage
Le widget ne se met pas à jour
iOS : Attendez jusqu'à 30 minutes, puis supprimez et rajoutez-le.
Android : Appuyez sur le widget pour forcer le rafraîchissement, vérifiez l'ID dans les paramètres.
watchOS : Redémarrez l'application de la montre, attendez quelques minutes.
Le widget affiche une erreur
- Vérifiez que ServerBox Monitor fonctionne
- Testez l'URL dans un navigateur
- Vérifiez que l'URL se termine par /status
Sécurité
- Utilisez toujours HTTPS si possible
- Adresses IP locales uniquement sur les réseaux de confiance
---
Src/Content/Docs/Fr/Platforms/Desktop
---
title: Fonctionnalités de bureau
description: Fonctionnalités spécifiques à macOS, Linux et Windows
---
Server Box sur les plateformes de bureau offre des fonctionnalités de productivité supplémentaires.
macOS
Intégration de la barre de menus
- État rapide du serveur dans la barre de menus
- Accès au serveur en un clic
- Mode compact pour une distraction minimale
- Style de barre de menus natif macOS
Persistance de l'état des fenêtres
- Mémorise la position et la taille de la fenêtre
- Restaurer la session précédente au lancement
- Prise en charge de plusieurs écrans
Fonctionnalités natives
- Barre de titre : Option de barre de titre personnalisée ou système
- Mode plein écran : Surveillance dédiée du serveur
- Raccourcis clavier : Raccourcis natifs macOS
- Touch Bar (appareils compatibles) : Actions rapides
Linux
Intégration native
- Prise en charge de la zone de notification (systray)
- Intégration des notifications de bureau
- Intégration du sélecteur de fichiers
Gestion des fenêtres
- Prise en charge de X11 et Wayland
- Compatible avec les gestionnaires de fenêtres à tuiles (tiling)
- Option de décorations de fenêtre personnalisées
Windows
Fonctionnalités
- Intégration de la zone de notification (systray)
- Actions rapides via la Jump List
- Contrôles de fenêtre natifs
- Option de démarrage automatique au boot
Fonctionnalités de bureau multiplateformes
Raccourcis clavier
- Cmd/Ctrl + N : Nouveau serveur
- Cmd/Ctrl + W : Fermer l'onglet
- Cmd/Ctrl + T : Nouvel onglet de terminal
- Cmd/Ctrl + , : Paramètres
Thèmes
- Thème clair
- Thème sombre
- Thème AMOLED (noir pur)
- Thème système (suit l'OS)
Fenêtres multiples
- Ouvrir plusieurs serveurs dans des fenêtres séparées
- Faire glisser des onglets vers une nouvelle fenêtre
- Comparer les statistiques des serveurs côte à côte
Avantages par rapport au mobile
- Écran plus grand pour la surveillance
- Clavier complet pour le terminal
- Opérations de fichiers plus rapides
- Meilleur multitâche
---
Src/Content/Docs/Fr/Platforms/Mobile
---
title: Fonctionnalités mobiles
description: Fonctionnalités spécifiques à iOS et Android
---
Server Box offre plusieurs fonctionnalités spécifiques aux mobiles pour les appareils iOS et Android.
Authentification biométrique
Sécurisez vos serveurs avec l'authentification biométrique :
- iOS : Face ID ou Touch ID
- Android : Authentification par empreinte digitale
Activez-la dans Paramètres > Sécurité > Authentification biométrique.
Widgets de l'écran d'accueil
Ajoutez des widgets d'état du serveur à votre écran d'accueil pour une surveillance rapide.
iOS
- Appui long sur l'écran d'accueil
- Appuyez sur + pour ajouter un widget
- Recherchez "Server Box"
- Choisissez la taille du widget :
- Petit : État d'un seul serveur
- Moyen : Plusieurs serveurs
- Grand : Informations détaillées
Android
- Appui long sur l'écran d'accueil
- Appuyez sur Widgets
- Trouvez "Server Box"
- Sélectionnez le type de widget
Fonctionnement en arrière-plan
Android
Maintenir les connexions actives en arrière-plan :
- Activer dans Paramètres > Avancé > Fonctionnement en arrière-plan
- Nécessite l'exclusion de l'optimisation de la batterie
- Notifications persistantes pour les connexions actives
iOS
Des limitations en arrière-plan s'appliquent :
- Les connexions peuvent se mettre en pause en arrière-plan
- Reconnexion rapide au retour dans l'application
- Prise en charge de l'actualisation en arrière-plan
Notifications Push
Recevez des notifications pour :
- Alertes de serveur hors ligne
- Avertissements d'utilisation élevée des ressources
- Alertes de fin de tâche
Configurez dans Paramètres > Notifications.
Fonctionnalités de l'interface mobile
- Tirer pour rafraîchir : Mettre à jour l'état du serveur
- Actions de glissement : Opérations rapides sur le serveur
- Mode paysage : Meilleure expérience du terminal
- Clavier virtuel : Raccourcis pour le terminal
Intégration de fichiers
- Application Fichiers (iOS) : Accès direct SFTP depuis Fichiers
- Storage Access Framework (Android) : Partager des fichiers avec d'autres applications
- Sélecteur de documents : Sélection de fichiers facile
---
Src/Content/Docs/Fr/Principles/Architecture
---
title: Présentation de l'architecture
description: Architecture de haut niveau de l'application
---
Server Box suit une architecture en couches avec une séparation claire des préoccupations.
Couches architecturales
┌─────────────────────────────────────────────────┐
│ Couche de présentation (UI) │
│ lib/view/page/, lib/view/widget/ │
│ - Pages, Widgets, Contrôleurs │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Couche logique métier │
│ lib/data/provider/ │
│ - Riverpod Providers, State Notifiers │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Couche d'accès aux données │
│ lib/data/store/, lib/data/model/ │
│ - Hive Stores, Modèles de données │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Couche d'intégration externe │
│ - SSH (dartssh2), Terminal (xterm), SFTP │
│ - Code spécifique à la plateforme (iOS, etc.) │
└─────────────────────────────────────────────────┘
Fondations de l'application
Point d'entrée principal
lib/main.dart initialise l'application :
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
Widget racine
MyApp fournit :
- Gestion des thèmes : Commutation entre thèmes clair/sombre
- Configuration du routage : Structure de navigation
- Provider Scope : Racine de l'injection de dépendances
Page d'accueil
HomePage sert de plaque tournante pour la navigation :
- Interface par onglets : Serveur, Snippet, Conteneur, SSH
- Gestion de l'état : État par onglet
- Navigation : Accès aux fonctionnalités
Systèmes de base
Gestion de l'état : Riverpod
Pourquoi Riverpod ?
- Sécurité au moment de la compilation
- Tests faciles
- Pas de dépendance au Build context
- Fonctionne sur toutes les plateformes
Types de Provider utilisés :
- StateProvider : État mutable simple
- AsyncNotifierProvider : États de chargement/erreur/données
- StreamProvider : Flux de données en temps réel
- Future providers : Opérations asynchrones uniques
Persistance des données : Hive CE
Pourquoi Hive CE ?
- Pas de dépendances de code natif
- Stockage clé-valeur rapide
- Type-safe avec génération de code
- Pas d'annotations de champs manuelles requises
Stores :
- SettingStore : Préférences de l'application
- ServerStore : Configurations de serveur
- SnippetStore : Extraits de commande
- KeyStore : Clés SSH
Modèles immuables : Freezed
Avantages :
- Immuabilité au moment de la compilation
- Types Union pour l'état
- Sérialisation JSON intégrée
- Extensions CopyWith
Stratégie multiplateforme
Système de plugins
Les plugins Flutter permettent l'intégration avec les plateformes :
| Plateforme | Méthode d'intégration |
|------------|----------------------|
| iOS | CocoaPods, Swift/Obj-C |
| Android | Gradle, Kotlin/Java |
| macOS | CocoaPods, Swift |
| Linux | CMake, C++ |
| Windows | CMake, C# |
Fonctionnalités spécifiques aux plateformes
iOS uniquement :
- Widgets de l'écran d'accueil
- Activités en direct (Live Activities)
- Compagnon Apple Watch
Android uniquement :
- Service en arrière-plan
- Notifications push
- Accès au système de fichiers
Bureau uniquement :
- Intégration de la barre de menus
- Fenêtres multiples
- Barre de titre personnalisée
Dépendances personnalisées
Fork de dartssh2
Client SSH amélioré avec :
- Meilleur support mobile
- Gestion des erreurs améliorée
- Optimisations de performance
Fork de xterm.dart
Émulateur de terminal avec :
- Rendu optimisé pour le mobile
- Support des gestes tactiles
- Intégration du clavier virtuel
fl_lib
Paquet d'utilitaires partagés avec :
- Widgets communs
- Extensions
- Fonctions d'aide
Système de construction
Paquet fl_build
Système de construction personnalisé pour :
- Constructions multiplateformes
- Signature de code
- Regroupement des ressources (assets)
- Gestion des versions
Processus de construction
make.dart (version) → fl_build (build) → Sortie plateforme
1. Pré-construction : Calculer la version à partir de Git
2. Construction : Compiler pour la plateforme cible
3. Post-construction : Paqueter et signerExemple de flux de données
Mise à jour de l'état du serveur
1. Le minuteur se déclenche →
2. Le Provider appelle le service →
3. Le service exécute la commande SSH →
4. La réponse est analysée en modèle →
5. L'état est mis à jour →
6. L'UI se reconstruit avec les nouvelles données
Flux d'action utilisateur
1. L'utilisateur appuie sur un bouton →
2. Le Widget appelle une méthode du provider →
3. Le Provider met à jour l'état →
4. Le changement d'état déclenche la reconstruction →
5. Le nouvel état est reflété dans l'UI
Architecture de sécurité
Protection des données
- Mots de passe : Chiffrés avec flutter_secure_storage
- Clés SSH : Chiffrées au repos
- Empreintes d'hôte : Stockées de manière sécurisée
- Données de session : Non persistées
Sécurité de la connexion
- Vérification de la clé d'hôte : Détection MITM (homme du milieu)
- Chiffrement : Chiffrement SSH standard
- Pas de texte clair : Les données sensibles ne sont jamais stockées en clair
---
Src/Content/Docs/Fr/Principles/Sftp
---
title: Système SFTP
description: Comment fonctionne le navigateur de fichiers SFTP
---
Le système SFTP fournit des capacités de gestion de fichiers via SSH.
Architecture
┌─────────────────────────────────────────────┐
│ Couche UI SFTP │
│ - Navigateur de fichiers (distant) │
│ - Navigateur de fichiers (local) │
│ - File d'attente de transfert │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Gestion de l'état SFTP │
│ - sftpProvider │
│ - Gestion des chemins │
│ - File d'attente d'opérations │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Couche protocole SFTP │
│ - Sous-système SSH │
│ - Opérations sur les fichiers │
│ - Liste des répertoires │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Transport SSH │
│ - Canal sécurisé │
│ - Streaming de données │
└─────────────────────────────────────────────┘
Établissement de la connexion
Création du client SFTP
Future<SftpClient> createSftpClient(Spi spi) async {
// 1. Obtenir le client SSH (réutiliser si disponible)
final sshClient = await genClient(spi);
// 2. Ouvrir le sous-système SFTP
final sftp = await sshClient.openSftp();
return sftp;
}
Réutilisation de la connexion
SFTP réutilise les connexions SSH existantes :
class ServerProvider {
SSHClient? _sshClient;
SftpClient? _sftpClient;
Future<SftpClient> getSftpClient(String spiId) async {
_sftpClient ??= await _sshClient!.openSftp();
return _sftpClient!;
}
}
Opérations du système de fichiers
Liste des répertoires
Future<List<SftpFile>> listDirectory(String path) async {
final sftp = await getSftpClient(spiId);
// Lister le répertoire
final files = await sftp.listDir(path);
// Trier selon les paramètres
files.sort((a, b) {
switch (sortOption) {
case SortOption.name:
return a.name.toLowerCase().compareTo(b.name.toLowerCase());
case SortOption.size:
return a.size.compareTo(b.size);
case SortOption.time:
return a.modified.compareTo(b.modified);
}
});
// Dossiers en premier si activé
if (showFoldersFirst) {
final dirs = files.where((f) => f.isDirectory);
final regular = files.where((f) => !f.isDirectory);
return [...dirs, ...regular];
}
return files;
}
Métadonnées de fichiers
class SftpFile {
final String name;
final String path;
final int size; // Octets
final int modified; // Horodatage Unix
final String permissions; // ex: "rwxr-xr-x"
final String owner;
final String group;
final bool isDirectory;
final bool isSymlink;
String get sizeFormatted => formatBytes(size);
String get modifiedFormatted => formatDate(modified);
}
Opérations sur les fichiers
Téléversement (Upload)
Future<void> uploadFile(
String localPath,
String remotePath,
) async {
final sftp = await getSftpClient(spiId);
// Créer la requête
final req = SftpReq(
spi: spi,
remotePath: remotePath,
localPath: localPath,
type: SftpReqType.upload,
);
// Ajouter à la file d'attente
_transferQueue.add(req);
// Exécuter le transfert avec progression
final file = File(localPath);
final size = await file.length();
final stream = file.openRead();
await sftp.upload(
stream: stream,
toPath: remotePath,
onProgress: (transferred) {
_updateProgress(req, transferred, size);
},
);
// Terminé
_transferQueue.remove(req);
}
Téléchargement (Download)
Future<void> downloadFile(
String remotePath,
String localPath,
) async {
final sftp = await getSftpClient(spiId);
// Créer le fichier local
final file = File(localPath);
final sink = file.openWrite();
// Télécharger avec progression
final stat = await sftp.stat(remotePath);
await sftp.download(
fromPath: remotePath,
toSink: sink,
onProgress: (transferred) {
_updateProgress(
SftpReq(...),
transferred,
stat.size,
);
},
);
await sink.close();
}
Édition des permissions
Future<void> setPermissions(
String path,
String permissions,
) async {
final sftp = await getSftpClient(spiId);
// Analyser les permissions (ex: "rwxr-xr-x" ou "755")
final mode = parsePermissions(permissions);
// Définir via commande SSH (plus fiable que SFTP)
final ssh = await getSshClient(spiId);
await ssh.exec('chmod $mode "$path"');
}
Gestion des chemins
Structure de chemin
class PathWithPrefix {
final String prefix; // ex: "/home/user"
final String path; // Relatif ou absolu
String get fullPath {
if (path.startsWith('/')) {
return path; // Chemin absolu
}
return '$prefix/$path'; // Chemin relatif
}
PathWithPrefix cd(String subPath) {
return PathWithPrefix(
prefix: fullPath,
path: subPath,
);
}
}
Historique de navigation
class PathHistory {
final List<String> _history = [];
int _index = -1;
void push(String path) {
// Supprimer l'historique suivant
_history.removeRange(_index + 1, _history.length);
_history.add(path);
_index = _history.length - 1;
}
String? back() {
if (_index > 0) {
_index--;
return _history[_index];
}
return null;
}
String? forward() {
if (_index < _history.length - 1) {
_index++;
return _history[_index];
}
return null;
}
}
Système de transfert
Requête de transfert
class SftpReq {
final Spi spi;
final String remotePath;
final String localPath;
final SftpReqType type;
final DateTime createdAt;
int? totalBytes;
int? transferredBytes;
String? error;
}
Suivi de progression
class TransferProgress {
final SftpReq request;
final int total;
final int transferred;
final DateTime startTime;
double get percentage => (transferred / total) * 100;
Duration get elapsed => DateTime.now().difference(startTime);
String get speedFormatted {
final bytesPerSecond = transferred / elapsed.inSeconds;
return formatSpeed(bytesPerSecond);
}
}
Gestion de la file d'attente
class TransferQueue {
final List<SftpReq> _queue = [];
final Map<String, TransferProgress> _progress = {};
int _concurrent = 3; // Nombre max de transferts simultanés
Future<void> process() async {
final active = _progress.values.where((p) => p.isInProgress);
if (active.length >= _concurrent) return;
final pending = _queue.where((r) => !_progress.containsKey(r.id));
for (final req in pending.take(_concurrent - active.length)) {
_executeTransfer(req);
}
}
Future<void> _executeTransfer(SftpReq req) async {
try {
_progress[req.id] = TransferProgress.inProgress(req);
if (req.type == SftpReqType.upload) {
await uploadFile(req.localPath, req.remotePath);
} else {
await downloadFile(req.remotePath, req.localPath);
}
_progress[req.id] = TransferProgress.completed(req);
} catch (e) {
_progress[req.id] = TransferProgress.failed(req, e);
}
}
}
Modèle de stockage local
Cache de téléchargement
Fichiers téléchargés stockés sur :
String getLocalDownloadPath(String spiId, String remotePath) {
final normalized = remotePath.replaceAll('/', '_');
return 'Paths.file/$spiId/$normalized';
}
Exemple :
- Distant : /var/log/nginx/access.log
- spiId : server-123
- Local : Paths.file/server-123/_var_log_nginx_access.logÉdition de fichiers
Flux d'édition
Future<void> editFile(String path) async {
final sftp = await getSftpClient(spiId);
// 1. Vérifier la taille
final stat = await sftp.stat(path);
if (stat.size > editorMaxSize) {
showWarning('Fichier trop volumineux pour l\'éditeur intégré');
return;
}
// 2. Télécharger vers dossier temp
final temp = await downloadToTemp(path);
// 3. Ouvrir dans l'éditeur
final content = await openEditor(temp.path);
// 4. Téléverser en retour
await uploadFile(temp.path, path);
// 5. Nettoyage
await temp.delete();
}
Intégration d'un éditeur externe
Future<void> editInExternalEditor(String path) async {
final ssh = await getSshClient(spiId);
// Ouvrir le terminal avec l'éditeur
final editor = getSetting('sftpEditor', 'vim');
await ssh.exec('$editor "$path"');
// L'utilisateur édite dans le terminal
// Après sauvegarde, rafraîchir la vue SFTP
}
Gestion des erreurs
Erreurs de permission
try {
await sftp.upload(...);
} on SftpPermissionException {
showError('Permission refusée : ${stat.path}');
showHint('Vérifiez les permissions et la propriété du fichier');
}
Erreurs de connexion
try {
await sftp.listDir(path);
} on SftpConnectionException {
showError('Connexion perdue');
await reconnect();
}
Erreurs d'espace disque
try {
await sftp.upload(...);
} on SftpNoSpaceException {
showError('Disque plein sur le serveur distant');
}
Optimisations de performance
Cache de répertoire
class DirectoryCache {
final Map<String, CachedDirectory> _cache = {};
final Duration ttl = Duration(minutes: 5);
Future<List<SftpFile>> list(String path) async {
final cached = _cache[path];
if (cached != null && !cached.isExpired) {
return cached.files;
}
final files = await sftp.listDir(path);
_cache[path] = CachedDirectory(files);
return files;
}
}
Chargement différé (Lazy Loading)
Pour les répertoires volumineux (>1000 éléments) :
List<SftpFile> loadPage(String path, int page, int pageSize) {
final all = cache[path] ?? [];
final start = page * pageSize;
final end = start + pageSize;
return all.sublist(start, end.clamp(0, all.length));
}
Pagination
class PaginatedDirectory {
static const pageSize = 100;
Future<List<SftpFile>> getPage(int page) async {
final offset = page * pageSize;
return await sftp.listDir(
path,
offset: offset,
limit: pageSize,
);
}
}
---Src/Content/Docs/Fr/Principles/Ssh
---
title: Connexion SSH
description: Comment les connexions SSH sont établies et gérées
---
Comprendre les connexions SSH dans Server Box.
Flux de connexion
Entrée utilisateur → Configuration Spi → genClient() → Client SSH → Session
Étape 1 : Configuration
Le modèle Spi (Server Parameter Info) contient :
class Spi {
String id; // Identifiant unique
String name; // Nom du serveur
String ip; // Adresse IP
int port; // Port SSH (par défaut 22)
String user; // Nom d'utilisateur
String? pwd; // Mot de passe (chiffré)
String? keyId; // ID de la clé SSH
String? jumpId; // ID du serveur de rebond (Jump server)
String? alterUrl; // URL alternative
}
Étape 2 : Génération du client
genClient(spi) crée le client SSH :
Future<SSHClient> genClient(Spi spi) async {
// 1. Établir le socket
var socket = await connect(spi.ip, spi.port);
// 2. Essayer l'URL alternative en cas d'échec
if (socket == null && spi.alterUrl != null) {
socket = await connect(spi.alterUrl, spi.port);
}
if (socket == null) {
throw ConnectionException('Unable to connect');
}
// 3. Authentifier
final client = SSHClient(
socket: socket,
username: spi.user,
onPasswordRequest: () => spi.pwd,
onIdentityRequest: () => loadKey(spi.keyId),
);
// 4. Vérifier la clé d'hôte
await verifyHostKey(client, spi);
return client;
}
Étape 3 : Serveur de rebond (si configuré)
Pour les serveurs de rebond, connexion récursive :
if (spi.jumpId != null) {
final jumpClient = await genClient(getJumpSpi(spi.jumpId));
final forwarded = await jumpClient.forwardLocal(
spi.ip,
spi.port,
);
// Se connecter via le socket transféré
}
Méthodes d'authentification
Authentification par mot de passe
onPasswordRequest: () => spi.pwd
- Mot de passe stocké chiffré dans Hive
- Déchiffré lors de la connexion
- Envoyé au serveur pour vérificationAuthentification par clé privée
onIdentityRequest: () async {
final key = await KeyStore.get(spi.keyId);
return decyptPem(key.pem, key.password);
}
Processus de chargement de la clé :
1. Récupérer la clé chiffrée depuis KeyStore
2. Déchiffrer le mot de passe (biométrie/invite)
3. Analyser le format PEM
4. Standardiser les fins de ligne (LF)
5. Retourner pour l'authentificationKeyboard-Interactive
onUserInfoRequest: (instructions) async {
// Gérer le challenge-response
return responses;
}
Supporte :
- L'authentification par mot de passe
- Les jetons OTP
- L'authentification à deux facteurs (2FA)Vérification de la clé d'hôte
Pourquoi vérifier les clés d'hôte ?
Empêche les attaques de type Man-in-the-Middle (MITM) en s'assurant que vous vous connectez au même serveur.
Format de stockage
{spi.id}::{keyType}
Exemple :mon-serveur::ssh-ed25519
mon-serveur::ecdsa-sha2-nistp256
Formats d'empreinte
MD5 Hex :
aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99
Base64 :SHA256:AbCdEf1234567890...=
Flux de vérification
Future<void> verifyHostKey(SSHClient client, Spi spi) async {
final key = await client.hostKey;
final keyType = key.type;
final fingerprint = md5Hex(key); // ou base64
final stored = SettingStore.sshKnownHostsFingerprints
['${spi.id}::$keyType'];
if (stored == null) {
// Nouvel hôte - inviter l'utilisateur
final trust = await promptUser(
'Hôte inconnu',
'Empreinte : $fingerprint',
);
if (trust) {
SettingStore.sshKnownHostsFingerprints
['${spi.id}::$keyType'] = fingerprint;
}
} else if (stored != fingerprint) {
// Modifié - avertir l'utilisateur
await warnUser(
'La clé d\'hôte a changé !',
'Attaque MITM possible',
);
}
}
Gestion des sessions
Mise en commun des connexions (Pooling)
Clients actifs maintenus dans ServerProvider :
class ServerProvider {
final Map<String, SSHClient> _clients = {};
SSHClient getClient(String spiId) {
return _clients[spiId] ??= connect(spiId);
}
}
Keep-Alive
Maintenir la connexion pendant l'inactivité :
Timer.periodic(
Duration(seconds: 30),
(_) => client.sendKeepAlive(),
);
Reconnexion automatique
En cas de perte de connexion :
client.onError.listen((error) async {
await Future.delayed(Duration(seconds: 5));
reconnect();
});
Cycle de vie de la connexion
┌─────────────┐
│ Initial │
└──────┬──────┘
│ connect()
↓
┌─────────────┐
│ Connexion │ ←──┐
└──────┬──────┘ │
│ succès │
↓ │ échec (retry)
┌─────────────┐ │
│ Connecté │───┘
└──────┬──────┘
│
↓
┌─────────────┐
│ Actif │ ──→ Envoyer des commandes
└──────┬──────┘
│
↓ (erreur/déconnexion)
┌─────────────┐
│ Déconnecté │
└─────────────┘
Gestion des erreurs
Délai d'attente de connexion (Timeout)
try {
await client.connect().timeout(
Duration(seconds: 30),
);
} on TimeoutException {
throw ConnectionException('Délai d\'attente de connexion dépassé');
}
Échec d'authentification
onAuthFail: (error) {
if (error.contains('password')) {
return 'Mot de passe invalide';
} else if (error.contains('key')) {
return 'Clé SSH invalide';
}
return 'Authentification échouée';
}
Discordance de clé d'hôte
onHostKeyMismatch: (stored, current) {
showSecurityWarning(
'La clé d\'hôte a changé !',
'Attaque MITM possible',
);
}
Considérations de performance
Réutilisation de la connexion
- Réutiliser les clients entre les fonctionnalités
- Ne pas déconnecter/reconnecter inutilement
- Mutualiser les connexions pour les opérations simultanées
Paramètres optimaux
- Timeout : 30 secondes (ajustable)
- Keep-alive : Toutes les 30 secondes
- Délai de relecture : 5 secondes
Efficacité du réseau
- Connexion unique pour plusieurs opérations
- Commandes en pipeline si possible
- Éviter d'ouvrir plusieurs connexions
---
Src/Content/Docs/Fr/Principles/State
---
title: Gestion de l'état
description: Comment l'état est géré avec Riverpod
---
Comprendre l'architecture de gestion de l'état dans Server Box.
Pourquoi Riverpod ?
Avantages clés :
- Sécurité à la compilation : Capture les erreurs lors de la compilation
- Pas de BuildContext requis : Accès à l'état n'importe où
- Tests faciles : Simple de tester les providers de manière isolée
- Génération de code : Moins de code répétitif, type-safe
Architecture des Providers
┌─────────────────────────────────────────────┐
│ Couche UI (Widgets) │
│ - ConsumerWidget / ConsumerStatefulWidget │
│ - ref.watch() / ref.read() │
└─────────────────────────────────────────────┘
↓ observe (watches)
┌─────────────────────────────────────────────┐
│ Couche Provider │
│ - Annotations @riverpod │
│ - Fichiers *.g.dart générés │
└─────────────────────────────────────────────┘
↓ utilise (uses)
┌─────────────────────────────────────────────┐
│ Couche Service / Store │
│ - Logique métier │
│ - Accès aux données │
└─────────────────────────────────────────────┘
Types de Providers utilisés
1. StateProvider (État simple)
Pour un état simple et observable :
@riverpod
class ThemeNotifier extends _$ThemeNotifier {
@override
ThemeMode build() {
// Charger depuis les paramètres
return SettingStore.themeMode;
}
void setTheme(ThemeMode mode) {
state = mode;
SettingStore.themeMode = mode; // Persister
}
}
Utilisation :class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themeNotifierProvider);
return Text('Thème : $theme');
}
}
2. AsyncNotifierProvider (État asynchrone)
Pour les données qui se chargent de manière asynchrone :
@riverpod
class ServerStatus extends _$ServerStatus {
@override
Future<StatusModel> build(Server server) async {
// Chargement initial
return await fetchStatus(server);
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return await fetchStatus(server);
});
}
}
Utilisation :final status = ref.watch(serverStatusProvider(server));
status.when(
data: (data) => StatusWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
3. StreamProvider (Données en temps réel)
Pour les flux de données continus :
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
final client = ref.watch(sshClientProvider(server));
final stream = client.monitorCpu();
// Libération automatique des ressources quand non observé
ref.onDispose(() {
client.stopMonitoring();
});
return stream;
}
Utilisation :final cpu = ref.watch(cpuUsageProvider(server));
cpu.when(
data: (usage) => CpuChart(usage),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
)
4. Family Providers (Paramétrés)
Providers qui acceptent des paramètres :
@riverpod
Future<List<Container>> containers(ContainersRef ref, Server server) async {
final client = await ref.watch(sshClientProvider(server).future);
return await client.listContainers();
}
Utilisation :final containers = ref.watch(containersProvider(server));
// Différents serveurs = différents états mis en cache
final containers2 = ref.watch(containersProvider(server2));
Optimisations de performance
- Provider Keep-Alive : Utilisez @Riverpod(keepAlive: true) pour empêcher la destruction automatique quand il n'y a plus d'écouteurs.
- Observation sélective : Utilisez select pour n'observer qu'une partie spécifique de l'état.
- Mise en cache des Providers : Les Family providers mettent en cache les résultats par paramètre.
Bonnes pratiques
1. Co-localiser les providers : Placez-les près des widgets qui les consomment.
2. Utiliser la génération de code : Utilisez toujours @riverpod.
3. Garder les providers focalisés : Responsabilité unique.
4. Gérer les états de chargement : Gérez toujours les états AsyncValue.
5. Libérer les ressources : Utilisez ref.onDispose() pour le nettoyage.
6. Éviter les arbres de providers profonds : Gardez le graphe des providers plat.
---
Src/Content/Docs/Fr/Principles/Terminal
---
title: Implémentation du terminal
description: Comment le terminal SSH fonctionne en interne
---
Le terminal SSH est l'une des fonctionnalités les plus complexes, basée sur un fork personnalisé de xterm.dart.
Présentation de l'architecture
┌─────────────────────────────────────────────┐
│ Couche UI du terminal │
│ - Gestion des onglets │
│ - Clavier virtuel │
│ - Sélection de texte │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Émulateur xterm.dart │
│ - PTY (Pseudo Terminal) │
│ - Émulation VT100/ANSI │
│ - Moteur de rendu │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Couche client SSH │
│ - Session SSH │
│ - Gestion des canaux │
│ - Streaming de données │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Serveur distant │
│ - Processus Shell │
│ - Exécution de commandes │
└─────────────────────────────────────────────┘
Cycle de vie d'une session de terminal
1. Création de la session
Future<TerminalSession> createSession(Spi spi) async {
// 1. Obtenir le client SSH
final client = await genClient(spi);
// 2. Créer le PTY
final pty = await client.openPty(
term: 'xterm-256color',
cols: 80,
rows: 24,
);
// 3. Initialiser l'émulateur de terminal
final terminal = Terminal(
backend: PtyBackend(pty),
);
// 4. Configurer le gestionnaire de redimensionnement
terminal.onResize.listen((size) {
pty.resize(size.cols, size.rows);
});
return TerminalSession(
terminal: terminal,
pty: pty,
client: client,
);
}
2. Émulation de terminal
Le fork xterm.dart fournit :
Émulation VT100/ANSI :
- Mouvement du curseur
- Couleurs (support 256 couleurs)
- Attributs de texte (gras, souligné, etc.)
- Régions de défilement
- Tampon d'écran alterné
Rendu :
- Rendu basé sur les lignes
- Support du texte bidirectionnel
- Support Unicode/emoji
- Redessins optimisés
3. Flux de données
Entrée utilisateur
↓
Clavier virtuel / Clavier physique
↓
Émulateur de terminal (touche → séquence d'échappement)
↓
Canal SSH (envoi)
↓
PTY distant
↓
Shell distant
↓
Sortie de commande
↓
Canal SSH (réception)
↓
Émulateur de terminal (analyse des codes ANSI)
↓
Rendu à l'écran
Système multi-onglets
Gestion des onglets
Les onglets maintiennent leur état lors de la navigation :
- Connexion SSH maintenue active
- État du terminal préservé
- Tampon de défilement conservé
- Historique de saisie retenu
Clavier virtuel
Implémentation spécifique à la plateforme
iOS :
- Clavier personnalisé basé sur UIView
- Basculable avec un bouton clavier
- Affichage/masquage automatique basé sur le focus
Android :
- Méthode de saisie personnalisée
- Intégré au clavier système
- Boutons d'action rapide
Boutons du clavier
| Bouton | Action |
|--------|--------|
| Basculer | Afficher/masquer le clavier système |
| Ctrl | Envoyer le modificateur Ctrl |
| Alt | Envoyer le modificateur Alt |
| SFTP | Ouvrir le répertoire courant |
| Presse-papiers | Copier/Coller contextuel |
| Snippets | Exécuter un extrait de code |
Sélection de texte
1. Appui long : Entrer en mode sélection
2. Glisser : Étendre la sélection
3. Relâcher : Copier dans le presse-papiers
Police et dimensions
Calcul de la taille
class TerminalDimensions {
static Size calculate(double fontSize, Size screenSize) {
final charWidth = fontSize * 0.6; // Ratio d'aspect monospace
final charHeight = fontSize * 1.2;
final cols = (screenSize.width / charWidth).floor();
final rows = (screenSize.height / charHeight).floor();
return Size(cols.toDouble(), rows.toDouble());
}
}
Pincer pour zoomer (Pinch-to-Zoom)
GestureDetector(
onScaleStart: () => _baseFontSize = currentFontSize,
onScaleUpdate: (details) {
final newFontSize = _baseFontSize * details.scale;
resize(newFontSize);
},
)
Schéma de couleurs
- Clair (Light) : Fond clair, texte sombre
- Sombre (Dark) : Fond sombre, texte clair
- AMOLED : Fond noir pur
Optimisations de performance
- Dirty rectangle : Ne redessiner que les régions modifiées
- Mise en cache des lignes : Mettre en cache les lignes rendues
- Défilement paresseux (Lazy scrolling) : Défilement virtuel pour les longs tampons
- Mises à jour par lots : Fusionner plusieurs écritures
- Compression : Compresser le tampon de défilement
- Anti-rebond (Debouncing) : Anti-rebond pour les saisies rapides
---
Src/Content/Docs/Fr/Index
---
title: Server Box
description: Une application complète de gestion de serveurs multiplateforme
hero:
tagline: Gérez vos serveurs Linux de n'importe où
actions:
- text: Commencer
link: /docs/fr/introduction/
icon: right-arrow
variant: primary
- text: Voir sur GitHub
link: https://github.com/lollipopkit/flutter_server_box
icon: github
variant: minimal
---
import { Card, CardGrid } from '@astrojs/starlight/components';
Fonctionnalités
<CardGrid stagger>
<Card title="Surveillance en temps réel" icon="chart">
Surveillez le CPU, la mémoire, le disque, le réseau, le GPU et la température avec de magnifiques graphiques en temps réel.
</Card>
<Card title="Terminal SSH" icon="terminal">
Terminal SSH complet avec support multi-onglets et clavier virtuel pour les appareils mobiles.
</Card>
<Card title="Navigateur de fichiers SFTP" icon="folder">
Gérez les fichiers sur vos serveurs avec le client SFTP intégré et le navigateur de fichiers local.
</Card>
<Card title="Gestion Docker" icon="box">
Démarrez, arrêtez et surveillez les conteneurs Docker avec une interface intuitive.
</Card>
<Card title="Multiplateforme" icon="device-mobile">
Disponible sur iOS, Android, macOS, Linux, Windows et watchOS.
</Card>
<Card title="Plus de 12 langues" icon="globe">
Support complet de localisation incluant l'anglais, le chinois, l'allemand, le français et plus encore.
</Card>
</CardGrid>
Liens rapides
- Téléchargement: Disponible sur l'App Store, GitHub, F-Droid, CDN et OpenAPK
- Documentation: Explorez les guides pour commencer avec Server Box
- Support: Rejoignez notre communauté sur GitHub pour des discussions et des problèmes
---
Src/Content/Docs/Fr/Installation
---
title: Installation
description: Téléchargez et installez Server Box sur votre appareil
---
Server Box est disponible sur plusieurs plateformes. Choisissez votre méthode d'installation préférée.
Applications Mobiles
iOS
Téléchargez depuis l'App Store.
Android
Choisissez votre source préférée :
- GitHub Releases - Pour la dernière version directement depuis la source
- CDN - Miroir pour les paquets de publication
- F-Droid - Pour les utilisateurs qui préfèrent les sources exclusivement FOSS
- OpenAPK - Boutique Android tierce
Applications de Bureau
macOS
Téléchargez depuis l’App Store ou installez avec Homebrew Cask :
brew install --cask server-box
Caractéristiques :
- Intégration native de la barre de menus
- Prise en charge d'Intel et d'Apple SiliconLinux
Téléchargez depuis les GitHub Releases ou le CDN.
GitHub Releases et le CDN fournissent des paquets Linux AppImage.
Windows
Téléchargez depuis les GitHub Releases ou le CDN.
GitHub Releases et le CDN fournissent des paquets Windows zip.
watchOS
Disponible sur l'App Store en tant que partie de l'application iOS.
Construction à partir des sources
Pour construire Server Box à partir des sources, consultez la section Construction dans la documentation de développement.
Informations sur la version
Consultez la page GitHub Releases pour la dernière version et le journal des modifications (changelog).
---
Src/Content/Docs/Fr/Introduction
---
title: Introduction
description: Découvrez ce qu'est Server Box et ce qu'il peut faire
---
Server Box est une application complète de gestion de serveur multiplateforme construite avec Flutter. Elle vous permet de surveiller, gérer et contrôler vos serveurs Linux, Unix et Windows de n'importe où.
Qu'est-ce que Server Box ?
Server Box fournit une interface unifiée pour les tâches d'administration de serveur via des connexions SSH. Que vous soyez un administrateur système, un développeur ou un passionné gérant des serveurs domestiques, cette application met de puissants outils de gestion de serveur dans votre poche.
Capacités clés
- Surveillance en temps réel : Suivez le processeur (CPU), la mémoire, l'utilisation du disque, la vitesse du réseau, l'état du GPU et les températures du système.
- Terminal SSH : Accès complet au terminal avec prise en charge multi-onglets et apparence personnalisable.
- Client SFTP : Parcourez et gérez les fichiers sur vos serveurs.
- Gestion Docker : Contrôlez les conteneurs en toute simplicité.
- Gestion des processus : Visualisez et gérez les processus système.
- Services Systemd : Démarrez, arrêtez et surveillez les services systemd.
- Outils réseau : Tests iPerf, ping et Wake-on-LAN.
- Snippets : Enregistrez et exécutez des commandes shell personnalisées.
Plateformes supportées
Server Box est véritablement multiplateforme :
- Mobile : iOS et Android
- Bureau : macOS, Linux et Windows
Licence
Ce projet est sous licence AGPL v3. Le code source est disponible sur GitHub.
---
Src/Content/Docs/Fr/Quick Start
---
title: Démarrage Rapide
description: Soyez opérationnel avec Server Box en quelques minutes
---
Suivez ce guide de démarrage rapide pour vous connecter à votre premier serveur et commencer la surveillance.
Étape 1 : Ajouter un serveur
1. Ouvrez Server Box
2. Appuyez sur le bouton + pour ajouter un nouveau serveur
3. Remplissez les informations du serveur :
- Nom : Un nom convivial pour votre serveur
- Hôte : Adresse IP ou nom de domaine
- Port : Port SSH (par défaut : 22)
- Utilisateur : Nom d'utilisateur SSH
- Mot de passe ou Clé : Méthode d'authentification
4. Appuyez sur Enregistrer pour ajouter le serveur
Étape 2 : Connecter et surveiller
1. Appuyez sur la carte de votre serveur pour vous connecter
2. L'application établira une connexion SSH
3. Vous verrez le statut en temps réel pour :
- L'utilisation du processeur (CPU)
- La mémoire (RAM) et le Swap
- L'utilisation du disque
- La vitesse du réseau
Étape 3 : Explorer les fonctionnalités
Une fois connecté, vous pouvez :
- Ouvrir le terminal : Appuyez sur le bouton du terminal pour un accès SSH complet
- Parcourir les fichiers : Utilisez SFTP pour gérer les fichiers
- Gérer les conteneurs : Visualisez et contrôlez les conteneurs Docker
- Afficher les processus : Vérifiez les processus en cours d'exécution
- Exécuter des snippets : Exécutez des commandes enregistrées
Conseils
- Authentification biométrique : Activez Face ID / Touch ID / Empreinte digitale pour un accès rapide (mobile)
- Widgets de l'écran d'accueil : Ajoutez des widgets d'état du serveur à votre écran d'accueil (iOS/Android)
- Fonctionnement en arrière-plan : Maintenez les connexions actives en arrière-plan (Android)
---
Src/Content/Docs/Es/Development/Architecture
---
title: Arquitectura
description: Patrones de arquitectura y decisiones de diseño
---
Server Box sigue los principios de Clean Architecture con una clara separación entre las capas de datos, dominio y presentación.
Arquitectura por Capas
┌─────────────────────────────────────┐
│ Capa de Presentación │
│ (lib/view/page/) │
│ - Páginas, Widgets, Controladores │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Capa de Lógica de Negocio │
│ (lib/data/provider/) │
│ - Riverpod Providers │
│ - Gestión de Estado │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Capa de Datos │
│ (lib/data/model/, store/) │
│ - Modelos, Almacén, Servicios │
└─────────────────────────────────────┘
Patrones Clave
Gestión de Estado: Riverpod
- Generación de Código: Usa riverpod_generator para providers con tipado seguro
- State Notifiers: Para estados mutables con lógica de negocio
- Async Notifiers: Para estados de carga y error
- Stream Providers: Para datos en tiempo real
Modelos Inmutables: Freezed
- Todos los modelos de datos usan Freezed para inmutabilidad
- Tipos Union para representación de estados
- Serialización JSON integrada
- Extensiones CopyWith para actualizaciones
Almacenamiento Local: Hive
- hive_ce: Edición comunitaria de Hive
- Sigue el patrón existente: la mayoría de los stores usan hive_ce, mientras algunos modelos versionados aún declaran explícitamente @HiveType y @HiveField
- Adaptadores de tipo generados automáticamente
- Almacenamiento persistente clave-valor
Inyección de Dependencias
Los servicios y almacenes se inyectan a través de:
1. Providers: Exponen dependencias a la UI
2. GetIt: Localizador de servicios (donde sea aplicable)
3. Inyección en Constructor: Dependencias explícitas
Flujo de Datos
Acción de Usuario → Widget → Provider → Servicio/Almacén → Actualización de Modelo → Reconstrucción de UI
1. El usuario interactúa con el widget
2. El widget llama al método del provider
3. El provider actualiza el estado a través del servicio/almacén
4. El cambio de estado activa la reconstrucción de la UI
5. El nuevo estado se refleja en el widgetDependencias Personalizadas
El proyecto utiliza varias ramas (forks) personalizadas para extender la funcionalidad:
- dartssh2: Funciones SSH mejoradas
- xterm: Emulador de terminal con soporte móvil
- fl_lib: Componentes de UI y utilidades compartidas
Multihilo
- Isolates: Computación pesada fuera del hilo principal
- paquete computer: Utilidades para multihilo
- Async/Await: Operaciones de E/S no bloqueantes
---
Src/Content/Docs/Es/Development/Building
---
title: Compilación
description: Instrucciones de compilación para diferentes plataformas
---
Server Box utiliza un sistema de compilación personalizado (fl_build) para compilaciones multiplataforma.
Requisitos Previos
- Flutter SDK (canal stable)
- Herramientas específicas de cada plataforma (Xcode para iOS, Android Studio para Android)
- Cadena de herramientas de Rust (para algunas dependencias nativas)
Compilación de Desarrollo
Ejecutar en modo desarrollo
flutter run
Ejecutar en un dispositivo específico
flutter run -d <id-del-dispositivo>
Compilación de Producción
El proyecto utiliza fl_build para compilar:
Compilar para una plataforma específica
dart run fl_build -p <plataforma>
Plataformas disponibles:
- ios
- android
- macos
- linux
- windows
Compilaciones Específicas por Plataforma
iOS
dart run fl_build -p ios
Requiere:
- macOS con Xcode
- CocoaPods
- Cuenta de Apple Developer para la firmaAndroid
dart run fl_build -p android
Requiere:
- Android SDK
- Java Development Kit
- Keystore para la firmamacOS
dart run fl_build -p macos
Linux
dart run fl_build -p linux
Windows
dart run fl_build -p windows
Requiere Windows con Visual Studio.Pre/Post Compilación
El script make.dart se encarga de:
- Generación de metadatos
- Actualización de cadenas de versión
- Configuraciones específicas de plataforma
Solución de Problemas
Compilación Limpia
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub get
Discrepancia de Versión
Asegúrate de que todas las dependencias son compatibles:
flutter pub upgrade
Lista de Verificación de Lanzamiento
1. Actualizar la versión en pubspec.yaml
2. Ejecutar la generación de código
3. Ejecutar las pruebas
4. Compilar para todas las plataformas de destino
5. Probar en dispositivos físicos
6. Crear lanzamiento (release) en GitHub
---
Src/Content/Docs/Es/Development/Codegen
---
title: Generación de Código
description: Uso de build_runner para la generación de código
---
Server Box utiliza intensivamente la generación de código para modelos, gestión de estado y serialización.
Cuándo Ejecutar la Generación de Código
Ejecutar tras modificar:
- Modelos con la anotación @freezed
- Clases con @JsonSerializable
- Modelos de Hive
- Providers con @riverpod
- Localizaciones (archivos ARB)
Ejecutar la Generación de Código
Generar todo el código
dart run build_runner build --delete-conflicting-outputs
Limpiar la caché de generación
dart run build_runner clean
Luego regenerar
dart run build_runner build --delete-conflicting-outputs
)')" title="Copy section prompt for LLMs"> Copy SectionArchivos Generados
Freezed (
.freezed.dart)Modelos de datos inmutables con tipos Union:
@freezed
class ServerState with _$ServerState {
const factory ServerState.connected() = Connected;
const factory ServerState.disconnected() = Disconnected;
const factory ServerState.error(String message) = Error;
}
)')" title="Copy section prompt for LLMs"> Copy SectionSerialización JSON (
.g.dart)Generado por
json_serializable:
@JsonSerializable()
class Server {
final String id;
final String name;
final String host;
Server({required this.id, required this.name, required this.host});
factory Server.fromJson(Map<String, dynamic> json) =>
_$ServerFromJson(json);
Map<String, dynamic> toJson() => _$ServerToJson(this);
}
)')" title="Copy section prompt for LLMs"> Copy SectionProviders de Riverpod (
.g.dart)Generados a partir de la anotación
@riverpod:
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() => 0;
}
)')" title="Copy section prompt for LLMs"> Copy SectionAdaptadores de Hive (
.g.dart)Auto-generados para modelos de Hive (hive_ce):
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}
Generación de Localización
flutter gen-l10n
Genera lib/generated/l10n/ a partir de los archivos lib/l10n/*.arb.Consejos
- Usa --delete-conflicting-outputs para evitar conflictos
- Mantén los archivos generados en el control de versiones cuando este repositorio ya los sigue
- Nunca edites manualmente los archivos generados
---
Src/Content/Docs/Es/Development/State
---
title: Gestión de Estado
description: Patrones de gestión de estado basados en Riverpod
---
Server Box utiliza Riverpod con generación de código para la gestión de estado.
Tipos de Provider
StateProvider
Estado simple que se puede leer y escribir:
@riverpod
class Settings extends _$Settings {
@override
SettingsModel build() {
return SettingsModel.defaults();
}
void update(SettingsModel newSettings) {
state = newSettings;
}
}
AsyncNotifierProvider
Estado que se carga de forma asíncrona con estados de carga/error:
@riverpod
class ServerStatus extends _$ServerStatus {
@override
Future<StatusModel> build(Server server) async {
return fetchStatus(server);
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => fetchStatus(server));
}
}
StreamProvider
Datos en tiempo real desde flujos (streams):
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}
Patrones de Estado
Estados de Carga
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
Family Providers
Providers parametrizados:
@riverpod
List<Container> containers(ContainersRef ref, Server server) {
return containerService.list(server);
}
Auto-Dispose
Providers que se eliminan cuando ya no están referenciados:
@Riverpod(keepAlive: false)
class TempState extends _$TempState {
// ...
}
Mejores Prácticas
1. Usar generación de código: Usa siempre la anotación @riverpod
2. Co-localizar providers: Ponlos cerca de los widgets que los consumen
3. Evitar singletons: Usa providers en su lugar
4. Capas correctas: Mantén la lógica de UI separada de la lógica de negocio
Leer el Estado en Widgets
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}
Modificar el Estado
ref.read(settingsProvider.notifier).update(newSettings);
---Src/Content/Docs/Es/Development/Structure
---
title: Estructura del Proyecto
description: Comprendiendo la base de código de Server Box
---
El proyecto Server Box sigue una arquitectura modular con una clara separación de responsabilidades.
Estructura de Directorios
lib/
├── core/ # Utilidades centrales y extensiones
├── data/ # Capa de datos
│ ├── model/ # Modelos de datos por función
│ ├── provider/ # Riverpod providers
│ └── store/ # Almacenamiento local (Hive)
├── view/ # Capa de UI
│ ├── page/ # Páginas principales
│ └── widget/ # Widgets reutilizables
├── generated/ # Localización generada
├── l10n/ # Archivos ARB de localización
└── hive/ # Adaptadores de Hive
)')" title="Copy chapter prompt for LLMs"> Copy ChapterCapa Central (
lib/core/)Contiene utilidades, extensiones y configuración de rutas:
- Extensions: Extensiones de Dart para tipos comunes
- Routes: Configuración de rutas de la app
- Utils: Funciones de utilidad compartidasCapa de Datos (
lib/data/)Modelos (
lib/data/model/)Organizados por función:
-
server/- Modelos de conexión y estado del servidor
-container/- Modelos de contenedores Docker
-ssh/- Modelos de sesión SSH
-sftp/- Modelos de archivos SFTP
-app/- Modelos específicos de la appProviders (
lib/data/provider/)Providers de Riverpod para inyección de dependencias y gestión de estado:
- Providers de servidor
- Providers de estado de UI
- Providers de serviciosAlmacenes (
lib/data/store/)Almacenamiento local basado en Hive:
- Almacén de servidores
- Almacén de ajustes
- Almacén de cachéCapa de Vista (
lib/view/)Páginas (
lib/view/page/)Pantallas principales de la aplicación:
-
server/- Páginas de gestión de servidores
-ssh/- Páginas de terminal SSH
-container/- Páginas de contenedores
-setting/- Páginas de ajustes
-storage/- Páginas de SFTP
-snippet/- Páginas de fragmentos (snippets)Widgets (
lib/view/widget/)Componentes de UI reutilizables:
- Tarjetas de servidor
- Gráficos de estado
- Componentes de entrada
- DiálogosArchivos Generados
-
lib/generated/l10n/- Localización auto-generada
-*.g.dart- Código generado (json_serializable, freezed, hive, riverpod)
-*.freezed.dart- Clases inmutables de FreezedDirectorio de Paquetes (
/packages/)Contiene ramas (forks) personalizadas de las dependencias:
-
dartssh2/- Librería SSH
-xterm/- Emulador de terminal
-fl_lib/- Utilidades compartidas
-fl_build/- Sistema de compilación---
Src/Content/Docs/Es/Development/Testing
---
title: Pruebas
description: Estrategias de prueba y ejecución de pruebas
---Ejecución de Pruebas
Ejecutar todas las pruebas
flutter test
Ejecutar un archivo de prueba específico
flutter test test/battery_test.dart
Ejecutar con cobertura
flutter test --coverage
Estructura de las Pruebas
Las pruebas se encuentran en el directorio test/. La suite actual es mayormente plana y se agrupa por comportamiento de parsers, modelos y utilidades, por ejemplo cpu_test.dart, container_test.dart y ssh_config_test.dart.
Pruebas Unitarias
Probar la lógica de negocio y los modelos de datos:
test('debería calcular el porcentaje de CPU', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});
Pruebas de Widgets
Probar componentes de la interfaz de usuario (UI):
testWidgets('ServerCard muestra el nombre del servidor', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: ServerCard(server: testServer),
),
),
);
expect(find.text('Test Server'), findsOneWidget);
});
Pruebas de Providers
Probar providers de Riverpod:
test('serverStatusProvider devuelve el estado', () async {
final container = ProviderContainer();
final status = await container.read(serverStatusProvider(testServer).future);
expect(status, isA<StatusModel>());
});
Dependencias externas
Evita pruebas que dependan de servidores SSH reales. Las pruebas de parsers, modelos y constructores de comandos deben ser deterministas; añade fakes o fixtures dirigidos cuando una función introduzca una frontera de servicio.
Pruebas de Integración
El repositorio actual no contiene una suite integration_test/. Añade pruebas de integración solo cuando una función necesite cobertura end-to-end de dispositivo o flujo completo de la app.
Buenas Prácticas
1. Arrange-Act-Assert: Estructurar las pruebas claramente.
2. Nombres descriptivos: Los nombres de las pruebas deben describir el comportamiento.
3. Una aserción por prueba: Mantener las pruebas enfocadas.
4. Simular dependencias externas: No depender de servidores reales.
5. Probar casos límite: Listas vacías, valores nulos, etc.
---
Src/Content/Docs/Es/Advanced/Bulk Import
---
title: Importación Masiva de Servidores
description: Importar múltiples servidores desde un archivo JSON
---
Importa múltiples configuraciones de servidor a la vez utilizando un archivo JSON.
Formato JSON
:::danger[Advertencia de Seguridad]
¡Nunca guardes contraseñas en texto plano en archivos! Este ejemplo JSON muestra un campo de contraseña solo con fines demostrativos, pero deberías:
- Preferir claves SSH (pubKeyId) en lugar de pwd; son más seguras
- Usar gestores de secretos o variables de entorno si debes usar contraseñas
- Eliminar el archivo inmediatamente después de la importación; no dejes credenciales tiradas
- Añadir a .gitignore: nunca subas archivos de credenciales al control de versiones
:::
[
{
"name": "Mi Servidor",
"ip": "example.com",
"port": 22,
"user": "root",
"pwd": "password",
"pubKeyId": "",
"tags": ["production"],
"autoConnect": false
}
]
Campos
| Campo | Requerido | Descripción |
|-------|-----------|-------------|
| name | Sí | Nombre para mostrar |
| ip | Sí | Dominio o dirección IP |
| port | Sí | Puerto SSH (usualmente 22) |
| user | Sí | Usuario SSH |
| pwd | No | Contraseña (evitar - usar claves SSH en su lugar) |
| pubKeyId | No | ID de clave privada (de Claves Privadas - recomendado) |
| tags | No | Etiquetas de organización |
| autoConnect | No | Autoconexión al iniciar |
Pasos para Importar
1. Crea un archivo JSON con las configuraciones del servidor
2. Ajustes → Copia de seguridad → Importación masiva de servidores
3. Selecciona tu archivo JSON
4. Confirma la importación
Ejemplo
[
{
"name": "Producción",
"ip": "prod.example.com",
"port": 22,
"user": "admin",
"pubKeyId": "mi-clave",
"tags": ["production", "web"]
},
{
"name": "Desarrollo",
"ip": "dev.example.com",
"port": 2222,
"user": "dev",
"pubKeyId": "dev-clave",
"tags": ["development"]
}
]
Consejos
- Usa claves SSH en lugar de contraseñas cuando sea posible
- Prueba la conexión después de la importación
- Organiza con etiquetas para una gestión más sencilla
- Elimina el archivo JSON después de la importación
- Nunca subas archivos JSON con credenciales al control de versiones
---
Src/Content/Docs/Es/Advanced/Custom Commands
---
title: Comandos Personalizados
description: Mostrar la salida de comandos personalizados en la página del servidor
---
Añade comandos shell personalizados para mostrar su salida en la página de detalles del servidor.
Configuración
1. Ajustes del servidor → Comandos personalizados
2. Introduce los comandos en formato JSON
Formato Básico
{
"Nombre a mostrar": "comando shell"
}
Ejemplo:{
"Memoria": "free -h",
"Disco": "df -h",
"Tiempo de actividad": "uptime"
}
Ver Resultados
Tras la configuración, los comandos personalizados aparecerán en la página de detalles del servidor y se actualizarán automáticamente.
Nombres de Comando Especiales
server_card_top_right
Se muestra en la tarjeta del servidor de la página de inicio (esquina superior derecha):
{
"server_card_top_right": "tu-comando-aquí"
}
Consejos
Usa rutas absolutas:
{"Mi Script": "/usr/local/bin/mi-script.sh"}
Comandos con tuberías (pipes):{"Proceso principal": "ps aux | sort -rk 3 | head -5"}
Formatear salida:{"Carga de CPU": "uptime | awk -F'load average:' '{print $2}'"}
Mantén los comandos rápidos: Menos de 5 segundos para una mejor experiencia.Limitar salida:
{"Logs": "tail -20 /var/log/syslog"}
Seguridad
Los comandos se ejecutan con los permisos del usuario SSH. Evita comandos que modifiquen el estado del sistema.
---
Src/Content/Docs/Es/Advanced/Custom Logo
---
title: Logo de Servidor Personalizado
description: Usa imágenes personalizadas para las tarjetas de servidor
---
Muestra logos personalizados en las tarjetas de servidor mediante URLs de imagen.
Configuración
1. Ajustes del servidor → Logo personalizado
2. Introduce la URL de la imagen
Marcadores de posición de URL
{DIST} - Distribución Linux
Se reemplaza automáticamente por la distribución detectada:
https://ejemplo.com/{DIST}.png
Se convierte en: debian.png, ubuntu.png, arch.png, etc.{BRIGHT} - Tema
Se reemplaza automáticamente por el tema actual:
https://ejemplo.com/{BRIGHT}.png
Se convierte en: light.png o dark.pngCombinar ambos
https://ejemplo.com/{DIST}-{BRIGHT}.png
Se convierte en: debian-light.png, ubuntu-dark.png, etc.Consejos
- Usa formatos PNG o SVG
- Tamaño recomendado: de 64x64 a 128x128 píxeles
- Usa URLs HTTPS
- Mantén tamaños de archivo pequeños
Distribuciones Soportadas
debian, ubuntu, centos, fedora, opensuse, kali, alpine, arch, rocky, deepin, armbian, wrt
Lista completa: dist.dart
---
Src/Content/Docs/Es/Advanced/Json Settings
---
title: Ajustes Ocultos (JSON)
description: Accede a ajustes avanzados mediante el editor JSON
---
Algunos ajustes están ocultos en la interfaz de usuario pero son accesibles a través del editor JSON.
Acceso
Mantén pulsado Ajustes en el menú lateral para abrir el editor JSON.
Ajustes Ocultos Comunes
timeOut
Tiempo de espera de conexión en segundos.
{"timeOut": 10}
Tipo: entero | Predeterminado: 5 | Rango: 1-60recordHistory
Guardar historial (rutas SFTP, etc.).
{"recordHistory": true}
Tipo: booleano | Predeterminado: truetextFactor
Factor de escala de texto.
{"textFactor": 1.2}
Tipo: doble | Predeterminado: 1.0 | Rango: 0.8-1.5Encontrar Más Ajustes
Todos los ajustes están definidos en setting.dart.
Busca:
late final settingName = StoreProperty(box, 'settingKey', defaultValue);
⚠️ Importante
Antes de editar:
- Crea una copia de seguridad: unos ajustes incorrectos pueden hacer que la app no se abra
- Edita con cuidado: el JSON debe ser válido
Recuperación
Si la aplicación no se abre tras editar:
1. Borra los datos de la aplicación (último recurso)
2. Reinstala la aplicación
3. Restaura desde una copia de seguridad
---
Src/Content/Docs/Es/Advanced/Troubleshooting
---
title: Problemas Comunes
description: Soluciones a problemas frecuentes
---
Problemas de Conexión
SSH no conecta
Síntomas: Tiempo de espera agotado (timeout), conexión rechazada, fallo de autenticación
Soluciones:
1. Verificar el tipo de servidor: Solo se admiten sistemas tipo Unix (Linux, macOS, Android/Termux)
2. Probar manualmente: ssh usuario@servidor -p puerto
3. Comprobar el cortafuegos: El puerto 22 debe estar abierto
4. Verificar credenciales: Usuario y contraseña/clave correctos
Desconexiones frecuentes
Síntomas: El terminal se desconecta tras un periodo de inactividad
Soluciones:
1. Keep-alive del servidor:
# /etc/ssh/sshd_config
ClientAliveInterval 60
ClientAliveCountMax 3
2. Desactivar optimización de batería:
- MIUI: Batería → "Sin restricciones"
- Android: Ajustes → Aplicaciones → Desactivar optimización
- iOS: Activar actualización en segundo planoProblemas de Entrada
No se pueden escribir ciertos caracteres
Solución: Ajustes → Tipo de teclado → Cambiar a visiblePassword
Nota: Es posible que la entrada CJK (chino, japonés, coreano) no funcione tras este cambio.
Problemas de la Aplicación
La aplicación se cierra al iniciar
Síntomas: La aplicación no se abre, pantalla en negro
Causas: Ajustes corruptos, especialmente tras usar el editor JSON
Soluciones:
1. Borrar datos de la aplicación:
- Android: Ajustes → Aplicaciones → ServerBox → Borrar datos
- iOS: Eliminar y reinstalar
2. Restaurar copia de seguridad: Importar una copia de seguridad creada antes de cambiar los ajustes
Problemas con Copia de Seguridad/Restauración
La copia de seguridad no funciona:
- Comprobar espacio de almacenamiento
- Verificar que la aplicación tiene permisos de almacenamiento
- Probar una ubicación diferente
La restauración falla:
- Verificar la integridad del archivo de copia de seguridad
- Comprobar la compatibilidad de la versión de la aplicación
Problemas con Widgets
El Widget no se actualiza
iOS:
- Esperar hasta 30 minutos para la actualización automática
- Eliminar y volver a añadir el widget
- Comprobar que la URL termina en /status
Android:
- Pulsar el widget para forzar la actualización
- Verificar que el ID del widget coincide con la configuración en los ajustes de la aplicación
watchOS:
- Reiniciar la aplicación del reloj
- Esperar unos minutos tras cambiar la configuración
- Verificar el formato de la URL
El Widget muestra un error
- Verificar que ServerBox Monitor se está ejecutando en el servidor
- Probar la URL en un navegador
- Comprobar las credenciales de autenticación
Problemas de Rendimiento
La aplicación va lenta
Soluciones:
- Reducir la tasa de refresco en los ajustes
- Comprobar la velocidad de la red
- Desactivar servidores no utilizados
Alto consumo de batería
Soluciones:
- Aumentar los intervalos de refresco
- Desactivar la actualización en segundo plano
- Cerrar sesiones SSH no utilizadas
Obtener Ayuda
Si los problemas persisten:
1. Buscar en GitHub Issues: https://github.com/lollipopkit/flutter_server_box/issues
2. Crear nueva Issue: Incluir versión de la aplicación, plataforma y pasos para reproducir
3. Consultar la Wiki: Esta documentación y la Wiki de GitHub
---
Src/Content/Docs/Es/Advanced/Widgets
---
title: Widgets de Pantalla de Inicio
description: Añade widgets de estado del servidor a tu pantalla de inicio
---
Requiere tener instalado ServerBox Monitor en tus servidores.
Requisitos Previos
Instala primero ServerBox Monitor en tu servidor. Consulta la Wiki de ServerBox Monitor para ver las instrucciones de configuración.
Tras la instalación, tu servidor debería tener:
- Un punto de acceso (endpoint) HTTP/HTTPS
- El punto de acceso API /status
- Autenticación opcional
Formato de URL
https://tu-servidor.com/status
Debe terminar en /status.Widget de iOS
Configuración
1. Mantén pulsada la pantalla de inicio → Toca el símbolo +
2. Busca "ServerBox"
3. Elige el tamaño del widget
4. Mantén pulsado el widget → Editar widget
5. Introduce la URL terminada en /status
Notas
- Debe usar HTTPS (excepto IPs locales)
- Tasa máxima de refresco: 30 minutos (límite de iOS)
- Añade varios widgets para varios servidores
Widget de Android
Configuración
1. Mantén pulsada la pantalla de inicio → Widgets
2. Busca "ServerBox" → Añadir a la pantalla de inicio
3. Anota el número de ID del widget que aparece
4. Abre la app ServerBox → Ajustes
5. Toca en Configurar enlace de widget de inicio
6. Añade la entrada: Widget ID = URL de estado
Ejemplo:
- Clave (Key): 17
- Valor (Value): https://mi-servidor.com/status
7. Toca el widget en la pantalla de inicio para refrescarlo
Widget de watchOS
Configuración
1. Abre la app en el iPhone → Ajustes
2. Ajustes de iOS → App del Watch
3. Toca en Añadir URL
4. Introduce la URL terminada en /status
5. Espera a que la app del reloj se sincronice
Notas
- Prueba a reiniciar la app del reloj si no se actualiza
- Verifica que el teléfono y el reloj están conectados
Solución de Problemas
El Widget no se actualiza
iOS: Espera hasta 30 minutos, luego elimínalo y vuelve a añadirlo.
Android: Toca el widget para forzar el refresco, verifica el ID en los ajustes.
watchOS: Reinicia la app del reloj, espera unos minutos.
El Widget muestra un error
- Verifica que ServerBox Monitor se está ejecutando
- Prueba la URL en un navegador
- Comprueba que la URL termina en /status
Seguridad
- Usa siempre HTTPS cuando sea posible
- IPs locales solo en redes de confianza
---
Src/Content/Docs/Es/Platforms/Desktop
---
title: Funciones de Escritorio
description: Funciones específicas para macOS, Linux y Windows
---
Server Box en plataformas de escritorio ofrece funciones de productividad adicionales.
macOS
Integración en la Barra de Menús
- Estado rápido del servidor en la barra de menús
- Acceso al servidor con un solo clic
- Modo compacto para una mínima distracción
- Estilo nativo de la barra de menús de macOS
Persistencia del Estado de la Ventana
- Recuerda la posición y el tamaño de la ventana
- Restaura la sesión anterior al iniciar
- Soporte para múltiples monitores
Funciones Nativas
- Barra de título: Opción de barra de título personalizada o del sistema
- Modo pantalla completa: Monitorización dedicada del servidor
- Atajos de teclado: Atajos nativos de macOS
- Touch Bar (dispositivos compatibles): Acciones rápidas
Linux
Integración Nativa
- Soporte para bandeja del sistema (systray)
- Integración con notificaciones de escritorio
- Integración con el selector de archivos
Gestión de Ventanas
- Soporte para X11 y Wayland
- Compatible con gestores de ventanas en mosaico (tiling)
- Opción de decoraciones de ventana personalizadas
Windows
Funciones
- Integración en la bandeja del sistema
- Acciones rápidas en la Jump List
- Controles de ventana nativos
- Opción de inicio automático al arrancar
Funciones de Escritorio Multiplataforma
Atajos de Teclado
- Cmd/Ctrl + N: Nuevo servidor
- Cmd/Ctrl + W: Cerrar pestaña
- Cmd/Ctrl + T: Nueva pestaña de terminal
- Cmd/Ctrl + ,: Ajustes
Temas
- Tema claro
- Tema oscuro
- Tema AMOLED (negro puro)
- Tema del sistema (sigue al SO)
Múltiples Ventanas
- Abrir varios servidores en ventanas separadas
- Arrastrar pestañas a una nueva ventana
- Comparar estadísticas de servidores en paralelo
Ventajas sobre el Móvil
- Pantalla más grande para monitorización
- Teclado completo para la terminal
- Operaciones de archivos más rápidas
- Mejor multitarea
---
Src/Content/Docs/Es/Platforms/Mobile
---
title: Funciones Móviles
description: Funciones específicas para iOS y Android
---
Server Box proporciona varias funciones específicas para dispositivos móviles iOS y Android.
Autenticación Biométrica
Asegura tus servidores con autenticación biométrica:
- iOS: Face ID o Touch ID
- Android: Autenticación por huella dactilar
Actívalo en Ajustes > Seguridad > Autenticación biométrica.
Widgets de Pantalla de Inicio
Añade widgets de estado del servidor a tu pantalla de inicio para una monitorización rápida.
iOS
- Mantén pulsada la pantalla de inicio
- Toca en + para añadir un widget
- Busca "Server Box"
- Elige el tamaño del widget:
- Pequeño: Estado de un solo servidor
- Mediano: Múltiples servidores
- Grande: Información detallada
Android
- Mantén pulsada la pantalla de inicio
- Toca en Widgets
- Busca "Server Box"
- Selecciona el tipo de widget
Ejecución en Segundo Plano
Android
Mantén las conexiones activas en segundo plano:
- Actívalo en Ajustes > Avanzado > Ejecución en segundo plano
- Requiere exclusión de la optimización de batería
- Notificaciones persistentes para conexiones activas
iOS
Se aplican limitaciones de segundo plano:
- Las conexiones pueden pausarse en segundo plano
- Reconexión rápida al volver a la app
- Soporte para actualización en segundo plano
Notificaciones Push
Recibe notificaciones para:
- Alertas de servidor fuera de línea
- Avisos de alto uso de recursos
- Alertas de finalización de tareas
Configúralo en Ajustes > Notificaciones.
Funciones de UI Móvil
- Deslizar para refrescar: Actualiza el estado del servidor
- Acciones de deslizamiento: Operaciones rápidas de servidor
- Modo horizontal: Mejor experiencia de terminal
- Teclado virtual: Atajos de terminal
Integración de Archivos
- App Archivos (iOS): Acceso directo SFTP desde Archivos
- Storage Access Framework (Android): Comparte archivos con otras apps
- Selector de documentos: Selección de archivos sencilla
---
Src/Content/Docs/Es/Principles/Architecture
---
title: Descripción General de la Arquitectura
description: Arquitectura de alto nivel de la aplicación
---
Server Box sigue una arquitectura por capas con una clara separación de responsabilidades.
Capas de la Arquitectura
┌─────────────────────────────────────────────────┐
│ Capa de Presentación (UI) │
│ lib/view/page/, lib/view/widget/ │
│ - Páginas, Widgets, Controladores │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Capa de Lógica de Negocio │
│ lib/data/provider/ │
│ - Riverpod Providers, State Notifiers │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Capa de Acceso a Datos │
│ lib/data/store/, lib/data/model/ │
│ - Hive Stores, Modelos de Datos │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ Capa de Integración Externa │
│ - SSH (dartssh2), Terminal (xterm), SFTP │
│ - Código específico de plataforma (iOS, etc.) │
└─────────────────────────────────────────────────┘
Fundamentos de la Aplicación
Punto de Entrada Principal
lib/main.dart inicializa la aplicación:
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
Widget Raíz
MyApp proporciona:
- Gestión de Temas: Cambio entre tema claro/oscuro
- Configuración de Rutas: Estructura de navegación
- Provider Scope: Raíz para la inyección de dependencias
Página de Inicio
HomePage sirve como núcleo de navegación:
- Interfaz de Pestañas: Servidor, Snippet, Contenedor, SSH
- Gestión de Estado: Estado por pestaña
- Navegación: Acceso a funciones
Sistemas Principales
Gestión de Estado: Riverpod
¿Por qué Riverpod?
- Seguridad en tiempo de compilación
- Facilidad para realizar pruebas
- Sin dependencia del Build context
- Funciona en todas las plataformas
Tipos de Provider Utilizados:
- StateProvider: Estado mutable simple
- AsyncNotifierProvider: Estados de carga/error/datos
- StreamProvider: Flujos de datos en tiempo real
- Future providers: Operaciones asíncronas únicas
Persistencia de Datos: Hive CE
¿Por qué Hive CE?
- Sin dependencias de código nativo
- Almacenamiento clave-valor rápido
- Tipado seguro con generación de código
- Sin necesidad de anotaciones manuales de campos
Almacenes (Stores):
- SettingStore: Preferencias de la app
- ServerStore: Configuraciones de servidores
- SnippetStore: Fragmentos de comandos
- KeyStore: Claves SSH
Modelos Inmutables: Freezed
Beneficios:
- Inmutabilidad en tiempo de compilación
- Tipos Union para el estado
- Serialización JSON integrada
- Extensiones CopyWith
Estrategia Multiplataforma
Sistema de Plugins
Los plugins de Flutter proporcionan la integración con la plataforma:
| Plataforma | Método de Integración |
|------------|-----------------------|
| iOS | CocoaPods, Swift/Obj-C |
| Android | Gradle, Kotlin/Java |
| macOS | CocoaPods, Swift |
| Linux | CMake, C++ |
| Windows | CMake, C# |
Funciones Específicas por Plataforma
Solo iOS:
- Widgets de pantalla de inicio
- Actividades en Directo (Live Activities)
- Compañero de Apple Watch
Solo Android:
- Servicio en segundo plano
- Notificaciones push
- Acceso al sistema de archivos
Solo Escritorio:
- Integración en la barra de menús
- Múltiples ventanas
- Barra de título personalizada
Dependencias Personalizadas
Rama (Fork) de dartssh2
Cliente SSH mejorado con:
- Mejor soporte para móviles
- Gestión de errores mejorada
- Optimizaciones de rendimiento
Rama (Fork) de xterm.dart
Emulador de terminal con:
- Renderizado optimizado para móviles
- Soporte para gestos táctiles
- Integración con teclado virtual
fl_lib
Paquete de utilidades compartidas con:
- Widgets comunes
- Extensiones
- Funciones de ayuda
Sistema de Compilación
Paquete fl_build
Sistema de compilación personalizado para:
- Compilaciones multiplataforma
- Firma de código
- Empaquetado de recursos (assets)
- Gestión de versiones
Proceso de Compilación
make.dart (versión) → fl_build (compilación) → Salida de plataforma
1. Pre-compilación: Cálculo de la versión desde Git
2. Compilación: Compilar para la plataforma de destino
3. Post-compilación: Empaquetado y firmaEjemplo de Flujo de Datos
Actualización del Estado del Servidor
1. El temporizador se activa →
2. El Provider llama al servicio →
3. El servicio ejecuta el comando SSH →
4. La respuesta se analiza en el modelo →
5. Se actualiza el estado →
6. La UI se reconstruye con los nuevos datos
Flujo de Acción del Usuario
1. El usuario toca un botón →
2. El Widget llama al método del provider →
3. El Provider actualiza el estado →
4. El cambio de estado activa la reconstrucción →
5. El nuevo estado se refleja en la UI
Arquitectura de Seguridad
Protección de Datos
- Contraseñas: Cifradas con flutter_secure_storage
- Claves SSH: Cifradas en reposo
- Huellas de Host: Almacenadas de forma segura
- Datos de Sesión: No se persisten
Seguridad de Conexión
- Verificación de Clave de Host: Detección de MITM
- Cifrado: Cifrado SSH estándar
- Sin Texto Plano: Los datos sensibles nunca se almacenan en plano
---
Src/Content/Docs/Es/Principles/Sftp
---
title: Sistema SFTP
description: Cómo funciona el explorador de archivos SFTP
---
El sistema SFTP proporciona capacidades de gestión de archivos sobre SSH.
Arquitectura
┌─────────────────────────────────────────────┐
│ Capa UI de SFTP │
│ - Explorador de archivos (remoto) │
│ - Explorador de archivos (local) │
│ - Cola de transferencia │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Gestión de Estado SFTP │
│ - sftpProvider │
│ - Gestión de rutas │
│ - Cola de operaciones │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Capa de Protocolo SFTP │
│ - Subsistema SSH │
│ - Operaciones de archivos │
│ - Listado de directorios │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Transporte SSH │
│ - Canal seguro │
│ - Streaming de datos │
└─────────────────────────────────────────────┘
Establecimiento de la Conexión
Creación del Cliente SFTP
Future<SftpClient> createSftpClient(Spi spi) async {
// 1. Obtener cliente SSH (reutilizar si está disponible)
final sshClient = await genClient(spi);
// 2. Abrir subsistema SFTP
final sftp = await sshClient.openSftp();
return sftp;
}
Reutilización de Conexiones
SFTP reutiliza las conexiones SSH existentes:
class ServerProvider {
SSHClient? _sshClient;
SftpClient? _sftpClient;
Future<SftpClient> getSftpClient(String spiId) async {
_sftpClient ??= await _sshClient!.openSftp();
return _sftpClient!;
}
}
Operaciones del Sistema de Archivos
Listado de Directorios
Future<List<SftpFile>> listDirectory(String path) async {
final sftp = await getSftpClient(spiId);
// Listar directorio
final files = await sftp.listDir(path);
// Ordenar según ajustes
files.sort((a, b) {
switch (sortOption) {
case SortOption.name:
return a.name.toLowerCase().compareTo(b.name.toLowerCase());
case SortOption.size:
return a.size.compareTo(b.size);
case SortOption.time:
return a.modified.compareTo(b.modified);
}
});
// Carpetas primero si está activado
if (showFoldersFirst) {
final dirs = files.where((f) => f.isDirectory);
final regular = files.where((f) => !f.isDirectory);
return [...dirs, ...regular];
}
return files;
}
Metadatos de Archivo
class SftpFile {
final String name;
final String path;
final int size; // Bytes
final int modified; // Timestamp Unix
final String permissions; // ej., "rwxr-xr-x"
final String owner;
final String group;
final bool isDirectory;
final bool isSymlink;
String get sizeFormatted => formatBytes(size);
String get modifiedFormatted => formatDate(modified);
}
Operaciones de Archivo
Subida (Upload)
Future<void> uploadFile(
String localPath,
String remotePath,
) async {
final sftp = await getSftpClient(spiId);
// Crear petición
final req = SftpReq(
spi: spi,
remotePath: remotePath,
localPath: localPath,
type: SftpReqType.upload,
);
// Añadir a la cola
_transferQueue.add(req);
// Ejecutar transferencia con progreso
final file = File(localPath);
final size = await file.length();
final stream = file.openRead();
await sftp.upload(
stream: stream,
toPath: remotePath,
onProgress: (transferred) {
_updateProgress(req, transferred, size);
},
);
// Completar
_transferQueue.remove(req);
}
Descarga (Download)
Future<void> downloadFile(
String remotePath,
String localPath,
) async {
final sftp = await getSftpClient(spiId);
// Crear archivo local
final file = File(localPath);
final sink = file.openWrite();
// Descargar con progreso
final stat = await sftp.stat(remotePath);
await sftp.download(
fromPath: remotePath,
toSink: sink,
onProgress: (transferred) {
_updateProgress(
SftpReq(...),
transferred,
stat.size,
);
},
);
await sink.close();
}
Edición de Permisos
Future<void> setPermissions(
String path,
String permissions,
) async {
final sftp = await getSftpClient(spiId);
// Analizar permisos (ej., "rwxr-xr-x" o "755")
final mode = parsePermissions(permissions);
// Establecer vía comando SSH (más fiable que SFTP)
final ssh = await getSshClient(spiId);
await ssh.exec('chmod $mode "$path"');
}
Gestión de Rutas
Estructura de Rutas
class PathWithPrefix {
final String prefix; // ej., "/home/user"
final String path; // Relativa o absoluta
String get fullPath {
if (path.startsWith('/')) {
return path; // Ruta absoluta
}
return '$prefix/$path'; // Ruta relativa
}
PathWithPrefix cd(String subPath) {
return PathWithPrefix(
prefix: fullPath,
path: subPath,
);
}
}
Historial de Navegación
class PathHistory {
final List<String> _history = [];
int _index = -1;
void push(String path) {
// Eliminar historial hacia adelante
_history.removeRange(_index + 1, _history.length);
_history.add(path);
_index = _history.length - 1;
}
String? back() {
if (_index > 0) {
_index--;
return _history[_index];
}
return null;
}
String? forward() {
if (_index < _history.length - 1) {
_index++;
return _history[_index];
}
return null;
}
}
Sistema de Transferencia
Petición de Transferencia
class SftpReq {
final Spi spi;
final String remotePath;
final String localPath;
final SftpReqType type;
final DateTime createdAt;
int? totalBytes;
int? transferredBytes;
String? error;
}
Seguimiento de Progreso
class TransferProgress {
final SftpReq request;
final int total;
final int transferred;
final DateTime startTime;
double get percentage => (transferred / total) * 100;
Duration get elapsed => DateTime.now().difference(startTime);
String get speedFormatted {
final bytesPerSecond = transferred / elapsed.inSeconds;
return formatSpeed(bytesPerSecond);
}
}
Gestión de Colas
class TransferQueue {
final List<SftpReq> _queue = [];
final Map<String, TransferProgress> _progress = {};
int _concurrent = 3; // Transferencias concurrentes máx.
Future<void> process() async {
final active = _progress.values.where((p) => p.isInProgress);
if (active.length >= _concurrent) return;
final pending = _queue.where((r) => !_progress.containsKey(r.id));
for (final req in pending.take(_concurrent - active.length)) {
_executeTransfer(req);
}
}
Future<void> _executeTransfer(SftpReq req) async {
try {
_progress[req.id] = TransferProgress.inProgress(req);
if (req.type == SftpReqType.upload) {
await uploadFile(req.localPath, req.remotePath);
} else {
await downloadFile(req.remotePath, req.localPath);
}
_progress[req.id] = TransferProgress.completed(req);
} catch (e) {
_progress[req.id] = TransferProgress.failed(req, e);
}
}
}
Patrón de Almacenamiento Local
Caché de Descargas
Los archivos descargados se guardan en:
String getLocalDownloadPath(String spiId, String remotePath) {
final normalized = remotePath.replaceAll('/', '_');
return 'Paths.file/$spiId/$normalized';
}
Ejemplo:
- Remoto: /var/log/nginx/access.log
- spiId: server-123
- Local: Paths.file/server-123/_var_log_nginx_access.logEdición de Archivos
Flujo de Trabajo de Edición
Future<void> editFile(String path) async {
final sftp = await getSftpClient(spiId);
// 1. Comprobar tamaño
final stat = await sftp.stat(path);
if (stat.size > editorMaxSize) {
showWarning('Archivo demasiado grande para el editor integrado');
return;
}
// 2. Descargar a temporal
final temp = await downloadToTemp(path);
// 3. Abrir en editor
final content = await openEditor(temp.path);
// 4. Subir de nuevo
await uploadFile(temp.path, path);
// 5. Limpieza
await temp.delete();
}
Integración con Editor Externo
Future<void> editInExternalEditor(String path) async {
final ssh = await getSshClient(spiId);
// Abrir terminal con editor
final editor = getSetting('sftpEditor', 'vim');
await ssh.exec('$editor "$path"');
// El usuario edita en la terminal
// Tras guardar, refrescar la vista SFTP
}
Gestión de Errores
Errores de Permiso
try {
await sftp.upload(...);
} on SftpPermissionException {
showError('Permiso denegado: ${stat.path}');
showHint('Comprueba los permisos y la propiedad del archivo');
}
Erreores de Conexión
try {
await sftp.listDir(path);
} on SftpConnectionException {
showError('Conexión perdida');
await reconnect();
}
Errores de Espacio
try {
await sftp.upload(...);
} on SftpNoSpaceException {
showError('Disco lleno en el servidor remoto');
}
Optimizaciones de Rendimiento
Caché de Directorios
class DirectoryCache {
final Map<String, CachedDirectory> _cache = {};
final Duration ttl = Duration(minutes: 5);
Future<List<SftpFile>> list(String path) async {
final cached = _cache[path];
if (cached != null && !cached.isExpired) {
return cached.files;
}
final files = await sftp.listDir(path);
_cache[path] = CachedDirectory(files);
return files;
}
}
Carga Perezosa (Lazy Loading)
Para directorios grandes (>1000 elementos):
List<SftpFile> loadPage(String path, int page, int pageSize) {
final all = cache[path] ?? [];
final start = page * pageSize;
final end = start + pageSize;
return all.sublist(start, end.clamp(0, all.length));
}
Paginación
class PaginatedDirectory {
static const pageSize = 100;
Future<List<SftpFile>> getPage(int page) async {
final offset = page * pageSize;
return await sftp.listDir(
path,
offset: offset,
limit: pageSize,
);
}
}
---Src/Content/Docs/Es/Principles/Ssh
---
title: Conexión SSH
description: Cómo se establecen y gestionan las conexiones SSH
---
Entendiendo las conexiones SSH en Server Box.
Flujo de Conexión
Entrada de Usuario → Configuración Spi → genClient() → Cliente SSH → Sesión
Paso 1: Configuración
El modelo Spi (Server Parameter Info) contiene:
class Spi {
String id; // ID del servidor
String name; // Nombre del servidor
String ip; // Dirección IP
int port; // Puerto SSH (por defecto 22)
String user; // Usuario
String? pwd; // Contraseña (cifrada)
String? keyId; // ID de la clave SSH
String? jumpId; // ID del servidor de salto (Jump server)
String? alterUrl; // URL alternativa
}
Paso 2: Generación del Cliente
genClient(spi) crea el cliente SSH:
Future<SSHClient> genClient(Spi spi) async {
// 1. Establecer socket
var socket = await connect(spi.ip, spi.port);
// 2. Probar URL alternativa si falla
if (socket == null && spi.alterUrl != null) {
socket = await connect(spi.alterUrl, spi.port);
}
if (socket == null) {
throw ConnectionException('Unable to connect');
}
// 3. Autenticar
final client = SSHClient(
socket: socket,
username: spi.user,
onPasswordRequest: () => spi.pwd,
onIdentityRequest: () => loadKey(spi.keyId),
);
// 4. Verificar clave de host
await verifyHostKey(client, spi);
return client;
}
Paso 3: Servidor de Salto (si está configurado)
Para servidores de salto, conexión recursiva:
if (spi.jumpId != null) {
final jumpClient = await genClient(getJumpSpi(spi.jumpId));
final forwarded = await jumpClient.forwardLocal(
spi.ip,
spi.port,
);
// Conectar a través del socket reenviado
}
Métodos de Autenticación
Autenticación por Contraseña
onPasswordRequest: () => spi.pwd
- Contraseña almacenada cifrada en Hive
- Descifrada al conectar
- Enviada al servidor para verificaciónAutenticación por Clave Privada
onIdentityRequest: () async {
final key = await KeyStore.get(spi.keyId);
return decyptPem(key.pem, key.password);
}
Proceso de Carga de Clave:
1. Recuperar clave cifrada de KeyStore
2. Descifrar contraseña (biometría/aviso)
3. Analizar formato PEM
4. Estandarizar finales de línea (LF)
5. Retornar para autenticaciónInteracción por Teclado (Keyboard-Interactive)
onUserInfoRequest: (instructions) async {
// Gestionar desafío-respuesta
return responses;
}
Soporta:
- Autenticación por contraseña
- Tokens OTP
- Autenticación de doble factor (2FA)Verificación de Clave de Host
¿Por qué verificar las claves de host?
Evita ataques de Hombre en el Medio (MITM) asegurando que te conectas al mismo servidor.
Formato de Almacenamiento
{spi.id}::{keyType}
Ejemplo:mi-servidor::ssh-ed25519
mi-servidor::ecdsa-sha2-nistp256
Formatos de Huella Digital (Fingerprint)
MD5 Hex:
aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99
Base64:SHA256:AbCdEf1234567890...=
Flujo de Verificación
Future<void> verifyHostKey(SSHClient client, Spi spi) async {
final key = await client.hostKey;
final keyType = key.type;
final fingerprint = md5Hex(key); // o base64
final stored = SettingStore.sshKnownHostsFingerprints
['${spi.id}::$keyType'];
if (stored == null) {
// Nuevo host - preguntar al usuario
final trust = await promptUser(
'Host desconocido',
'Huella: $fingerprint',
);
if (trust) {
SettingStore.sshKnownHostsFingerprints
['${spi.id}::$keyType'] = fingerprint;
}
} else if (stored != fingerprint) {
// Ha cambiado - advertir al usuario
await warnUser(
'¡La clave de host ha cambiado!',
'Posible ataque MITM',
);
}
}
Gestión de Sesiones
Pool de Conexiones
Clientes activos mantenidos en ServerProvider:
class ServerProvider {
final Map<String, SSHClient> _clients = {};
SSHClient getClient(String spiId) {
return _clients[spiId] ??= connect(spiId);
}
}
Keep-Alive
Mantener la conexión durante la inactividad:
Timer.periodic(
Duration(seconds: 30),
(_) => client.sendKeepAlive(),
);
Reconexión Automática
Al perder la conexión:
client.onError.listen((error) async {
await Future.delayed(Duration(seconds: 5));
reconnect();
});
Ciclo de Vida de la Conexión
┌─────────────┐
│ Inicial │
└──────┬──────┘
│ connect()
↓
┌─────────────┐
│ Conectando │ ←──┐
└──────┬──────┘ │
│ éxito │
↓ │ fallo (reintento)
┌─────────────┐ │
│ Conectado │───┘
└──────┬──────┘
│
↓
┌─────────────┐
│ Activo │ ──→ Enviar comandos
└──────┬──────┘
│
↓ (error/desconexión)
┌─────────────┐
│ Desconectado│
└─────────────┘
Gestión de Errores
Tiempo de Espera Agotado (Timeout)
try {
await client.connect().timeout(
Duration(seconds: 30),
);
} on TimeoutException {
throw ConnectionException('Tiempo de espera de conexión agotado');
}
Fallo de Autenticación
onAuthFail: (error) {
if (error.contains('password')) {
return 'Contraseña no válida';
} else if (error.contains('key')) {
return 'Clave SSH no válida';
}
return 'Fallo de autenticación';
}
Discrepancia en Clave de Host
onHostKeyMismatch: (stored, current) {
showSecurityWarning(
'¡La clave de host ha cambiado!',
'Posible ataque MITM',
);
}
Consideraciones de Rendimiento
Reutilización de Conexiones
- Reutilizar clientes entre funciones
- No desconectar/reconectar innecesariamente
- Pool de conexiones para operaciones concurrentes
Ajustes Óptimos
- Timeout: 30 segundos (ajustable)
- Keep-alive: Cada 30 segundos
- Retraso de reintento: 5 segundos
Eficiencia de Red
- Conexión única para múltiples operaciones
- Comandos en tubería (pipeline) cuando sea posible
- Evitar abrir múltiples conexiones
---
Src/Content/Docs/Es/Principles/State
---
title: Gestión de Estado
description: Cómo se gestiona el estado con Riverpod
---
Entendiendo la arquitectura de gestión de estado en Server Box.
¿Por qué Riverpod?
Beneficios Clave:
- Seguridad en tiempo de compilación: Detecta errores al compilar
- Sin necesidad de BuildContext: Accede al estado desde cualquier lugar
- Facilidad de pruebas: Sencillo de probar providers de forma aislada
- Generación de código: Menos código repetitivo, tipado seguro
Arquitectura de Providers
┌─────────────────────────────────────────────┐
│ Capa UI (Widgets) │
│ - ConsumerWidget / ConsumerStatefulWidget │
│ - ref.watch() / ref.read() │
└─────────────────────────────────────────────┘
↓ observa (watches)
┌─────────────────────────────────────────────┐
│ Capa de Provider │
│ - Anotaciones @riverpod │
│ - Archivos *.g.dart generados │
└─────────────────────────────────────────────┘
↓ usa (uses)
┌─────────────────────────────────────────────┐
│ Capa de Servicio / Store │
│ - Lógica de negocio │
│ - Acceso a datos │
└─────────────────────────────────────────────┘
Tipos de Provider Utilizados
1. StateProvider (Estado Simple)
Para estados simples y observables:
@riverpod
class ThemeNotifier extends _$ThemeNotifier {
@override
ThemeMode build() {
// Cargar desde ajustes
return SettingStore.themeMode;
}
void setTheme(ThemeMode mode) {
state = mode;
SettingStore.themeMode = mode; // Persistir
}
}
Uso:class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themeNotifierProvider);
return Text('Tema: $theme');
}
}
2. AsyncNotifierProvider (Estado Asíncrono)
Para datos que se cargan de forma asíncrona:
@riverpod
class ServerStatus extends _$ServerStatus {
@override
Future<StatusModel> build(Server server) async {
// Carga inicial
return await fetchStatus(server);
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return await fetchStatus(server);
});
}
}
Uso:final status = ref.watch(serverStatusProvider(server));
status.when(
data: (data) => StatusWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
3. StreamProvider (Datos en Tiempo Real)
Para flujos de datos continuos:
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
final client = ref.watch(sshClientProvider(server));
final stream = client.monitorCpu();
// Liberación automática cuando no se observa
ref.onDispose(() {
client.stopMonitoring();
});
return stream;
}
Uso:final cpu = ref.watch(cpuUsageProvider(server));
cpu.when(
data: (usage) => CpuChart(usage),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
)
4. Family Providers (Parametrizados)
Providers que aceptan parámetros:
@riverpod
Future<List<Container>> containers(ContainersRef ref, Server server) async {
final client = await ref.watch(sshClientProvider(server).future);
return await client.listContainers();
}
Uso:final containers = ref.watch(containersProvider(server));
// Diferentes servidores = diferentes estados en caché
final containers2 = ref.watch(containersProvider(server2));
Optimizaciones de Rendimiento
- Provider Keep-Alive: Usa @Riverpod(keepAlive: true) para evitar que se destruya automáticamente cuando no haya escuchadores.
- Observación selectiva: Usa select para observar solo una parte específica del estado.
- Caché de Providers: Los Family providers cachean resultados por parámetro.
Mejores Prácticas
1. Co-localizar providers: Colócalos cerca de los widgets que los consumen.
2. Usar generación de código: Usa siempre @riverpod.
3. Mantener providers enfocados: Responsabilidad única.
4. Gestionar estados de carga: Maneja siempre los estados de AsyncValue.
5. Liberar recursos: Usa ref.onDispose() para la limpieza.
6. Evitar árboles de providers profundos: Mantén el grafo de providers plano.
---
Src/Content/Docs/Es/Principles/Terminal
---
title: Implementación de la Terminal
description: Cómo funciona internamente la terminal SSH
---
La terminal SSH es una de las funciones más complejas, construida sobre un fork personalizado de xterm.dart.
Resumen de la Arquitectura
┌─────────────────────────────────────────────┐
│ Capa de UI de la Terminal │
│ - Gestión de pestañas │
│ - Teclado virtual │
│ - Selección de texto │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Emulador xterm.dart │
│ - PTY (Pseudo Terminal) │
│ - Emulación VT100/ANSI │
│ - Motor de renderizado │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Capa de Cliente SSH │
│ - Sesión SSH │
│ - Gestión de canales │
│ - Streaming de datos │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Servidor Remoto │
│ - Proceso de Shell │
│ - Ejecución de comandos │
└─────────────────────────────────────────────┘
Ciclo de Vida de la Sesión de Terminal
1. Creación de la Sesión
Future<TerminalSession> createSession(Spi spi) async {
// 1. Obtener cliente SSH
final client = await genClient(spi);
// 2. Crear PTY
final pty = await client.openPty(
term: 'xterm-256color',
cols: 80,
rows: 24,
);
// 3. Inicializar emulador de terminal
final terminal = Terminal(
backend: PtyBackend(pty),
);
// 4. Configurar manejador de cambio de tamaño
terminal.onResize.listen((size) {
pty.resize(size.cols, size.rows);
});
return TerminalSession(
terminal: terminal,
pty: pty,
client: client,
);
}
2. Emulación de Terminal
El fork de xterm.dart proporciona:
Emulación VT100/ANSI:
- Movimiento del cursor
- Colores (soporte para 256 colores)
- Atributos de texto (negrita, subrayado, etc.)
- Regiones de desplazamiento
- Búfer de pantalla alternativo
Renderizado:
- Renderizado basado en líneas
- Soporte para texto bidireccional
- Soporte para Unicode/emoji
- Redibujado optimizado
3. Flujo de Datos
Entrada del Usuario
↓
Teclado Virtual / Teclado Físico
↓
Emulador de Terminal (tecla → secuencia de escape)
↓
Canal SSH (envío)
↓
PTY Remoto
↓
Shell Remoto
↓
Salida del Comando
↓
Canal SSH (recepción)
↓
Emulador de Terminal (analizar códigos ANSI)
↓
Renderizado en Pantalla
Sistema de Múltiples Pestañas
Gestión de Pestañas
Las pestañas mantienen su estado durante la navegación:
- La conexión SSH se mantiene activa
- Se preserva el estado de la terminal
- Se mantiene el búfer de desplazamiento
- Se retiene el historial de entrada
Teclado Virtual
Implementación Específica por Plataforma
iOS:
- Teclado personalizado basado en UIView
- Conmutable con un botón de teclado
- Mostrar/ocultar automáticamente basado en el enfoque
Android:
- Método de entrada personalizado
- Integrado con el teclado del sistema
- Botones de acción rápida
Botones del Teclado
| Botón | Acción |
|--------|--------|
| Conmutar | Mostrar/ocultar teclado del sistema |
| Ctrl | Enviar modificador Ctrl |
| Alt | Enviar modificador Alt |
| SFTP | Abrir directorio actual |
| Portapapeles | Copiar/Pegar sensible al contexto |
| Snippets | Ejecutar fragmento de código |
Selección de Texto
1. Pulsación larga: Entrar en modo selección
2. Arrastrar: Extender la selección
3. Soltar: Copiar al portapapeles
Fuente y Dimensiones
Cálculo de Tamaño
class TerminalDimensions {
static Size calculate(double fontSize, Size screenSize) {
final charWidth = fontSize * 0.6; // Relación de aspecto monoespaciada
final charHeight = fontSize * 1.2;
final cols = (screenSize.width / charWidth).floor();
final rows = (screenSize.height / charHeight).floor();
return Size(cols.toDouble(), rows.toDouble());
}
}
Pellizcar para Ampliar (Pinch-to-Zoom)
GestureDetector(
onScaleStart: () => _baseFontSize = currentFontSize,
onScaleUpdate: (details) {
final newFontSize = _baseFontSize * details.scale;
resize(newFontSize);
},
)
Esquema de Colores
- Claro (Light): Fondo claro, texto oscuro
- Oscuro (Dark): Fondo oscuro, texto claro
- AMOLED: Fondo negro puro
Optimizaciones de Rendimiento
- Dirty rectangle: Solo redibujar las regiones cambiadas
- Caché de líneas: Cachear las líneas renderizadas
- Desplazamiento perezoso (Lazy scrolling): Desplazamiento virtual para búferes largos
- Actualizaciones por lotes: Unificar múltiples escrituras
- Compresión: Comprimir el búfer de desplazamiento
- Debouncing: Antirrebote para entradas rápidas
---
Src/Content/Docs/Es/Index
---
title: Server Box
description: Una aplicación integral de gestión de servidores multiplataforma
hero:
tagline: Administra tus servidores Linux desde cualquier lugar
actions:
- text: Empezar
link: /docs/es/introduction/
icon: right-arrow
variant: primary
- text: Ver en GitHub
link: https://github.com/lollipopkit/flutter_server_box
icon: github
variant: minimal
---
import { Card, CardGrid } from '@astrojs/starlight/components';
Características
<CardGrid stagger>
<Card title="Monitoreo en Tiempo Real" icon="chart">
Monitorea CPU, memoria, disco, red, GPU y temperatura con hermosos gráficos en tiempo real.
</Card>
<Card title="Terminal SSH" icon="terminal">
Terminal SSH con todas las funciones, soporte para múltiples pestañas y teclado virtual para dispositivos móviles.
</Card>
<Card title="Navegador de Archivos SFTP" icon="folder">
Administra archivos en tus servidores con el cliente SFTP integrado y el navegador de archivos local.
</Card>
<Card title="Gestión de Docker" icon="box">
Inicia, detén y monitorea contenedores Docker con una interfaz intuitiva.
</Card>
<Card title="Multiplataforma" icon="device-mobile">
Disponible en iOS, Android, macOS, Linux, Windows y watchOS.
</Card>
<Card title="Más de 12 Idiomas" icon="globe">
Soporte completo de localización que incluye inglés, chino, alemán, francés y más.
</Card>
</CardGrid>
Enlaces Rápidos
- Descarga: Disponible en App Store, GitHub, F-Droid, CDN y OpenAPK
- Documentación: Explora las guías para comenzar con Server Box
- Soporte: Únete a nuestra comunidad en GitHub para discusiones y problemas
---
Src/Content/Docs/Es/Installation
---
title: Instalación
description: Descarga e instala Server Box en tu dispositivo
---
Server Box está disponible en múltiples plataformas. Elige tu método de instalación preferido.
Aplicaciones Móviles
iOS
Descárgalo desde la App Store.
Android
Elige tu fuente preferida:
- GitHub Releases - Para la última versión directamente desde la fuente
- CDN - Espejo para paquetes de lanzamiento
- F-Droid - Para usuarios que prefieren fuentes exclusivamente FOSS (Software Libre y de Código Abierto)
- OpenAPK - Tienda Android de terceros
Aplicaciones de Escritorio
macOS
Descárgalo desde la App Store o instálalo con Homebrew Cask:
brew install --cask server-box
Características:
- Integración nativa con la barra de menú
- Soporte para Intel y Apple SiliconLinux
Descárgalo desde GitHub Releases o el CDN.
GitHub Releases y el CDN proporcionan paquetes Linux AppImage.
Windows
Descárgalo desde GitHub Releases o el CDN.
GitHub Releases y el CDN proporcionan paquetes Windows zip.
watchOS
Disponible en la App Store como parte de la aplicación para iOS.
Compilación desde el Código Fuente
Para compilar Server Box desde el código fuente, consulta la sección de Compilación en la documentación de desarrollo.
Información de Versión
Consulta la página de GitHub Releases para ver la última versión y el registro de cambios.
---
Src/Content/Docs/Es/Introduction
---
title: Introducción
description: Aprende qué es Server Box y qué puede hacer
---
Server Box es una aplicación integral de gestión de servidores multiplataforma creada con Flutter. Te permite monitorear, gestionar y controlar tus servidores Linux, Unix y Windows desde cualquier lugar.
¿Qué es Server Box?
Server Box proporciona una interfaz unificada para tareas de administración de servidores a través de conexiones SSH. Ya seas un administrador de sistemas, desarrollador o entusiasta con servidores domésticos, esta aplicación pone potentes herramientas de gestión de servidores en tu bolsillo.
Capacidades Clave
- Monitoreo en Tiempo Real: Sigue el uso de CPU, memoria, disco, velocidad de red, estado de GPU y temperaturas del sistema.
- Terminal SSH: Acceso total a la terminal con soporte multi-pestaña y apariencia personalizable.
- Cliente SFTP: Explora y gestiona archivos en tus servidores.
- Gestión de Docker: Controla contenedores con facilidad.
- Gestión de Procesos: Visualiza y gestiona procesos del sistema.
- Servicios Systemd: Inicia, detén y monitorea servicios systemd.
- Herramientas de Red: Pruebas iPerf, ping y Wake-on-LAN.
- Snippets: Guarda y ejecuta comandos de shell personalizados.
Plataformas Soportadas
Server Box es verdaderamente multiplataforma:
- Móvil: iOS y Android
- Escritorio: macOS, Linux y Windows
Licencia
Este proyecto está bajo la licencia AGPL v3. El código fuente está disponible en GitHub.
---
Src/Content/Docs/Es/Quick Start
---
title: Inicio Rápido
description: Comienza a usar Server Box en cuestión de minutos
---
Sigue esta guía de inicio rápido para conectarte a tu primer servidor y comenzar la monitorización.
Paso 1: Agregar un Servidor
1. Abre Server Box
2. Toca el botón + para agregar un nuevo servidor
3. Completa la información del servidor:
- Nombre: Un nombre descriptivo para tu servidor
- Host: Dirección IP o nombre de dominio
- Puerto: Puerto SSH (por defecto: 22)
- Usuario: Nombre de usuario SSH
- Contraseña o Llave: Método de autenticación
4. Toca Guardar para agregar el servidor
Paso 2: Conectar y Monitorear
1. Toca en la tarjeta de tu servidor para conectarte
2. La aplicación establecerá una conexión SSH
3. Verás el estado en tiempo real de:
- Uso de CPU
- Memoria (RAM) y Swap
- Uso de disco
- Velocidad de red
Paso 3: Explorar Funcionalidades
Una vez conectado, puedes:
- Abrir la Terminal: Toca el botón de la terminal para obtener acceso SSH completo
- Explorar Archivos: Usa SFTP para gestionar archivos
- Gestionar Contenedores: Visualiza y controla contenedores Docker
- Ver Procesos: Revisa los procesos en ejecución
- Ejecutar Snippets: Ejecuta comandos guardados
Consejos
- Autenticación Biométrica: Activa Face ID / Touch ID / Huella dactilar para un acceso rápido (móvil)
- Widgets en la Pantalla de Inicio: Agrega widgets de estado del servidor a tu pantalla de inicio (iOS/Android)
- Ejecución en Segundo Plano: Mantén las conexiones activas en segundo plano (Android)
---
Src/Content/Docs/Development/Architecture
---
title: Architecture
description: Architecture patterns and design decisions
---
Server Box follows clean architecture principles with clear separation between data, domain, and presentation layers.
Layered Architecture
┌─────────────────────────────────────┐
│ Presentation Layer │
│ (lib/view/page/) │
│ - Pages, Widgets, Controllers │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Business Logic Layer │
│ (lib/data/provider/) │
│ - Riverpod Providers │
│ - State Management │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Data Layer │
│ (lib/data/model/, store/) │
│ - Models, Storage, Services │
└─────────────────────────────────────┘
Key Patterns
State Management: Riverpod
- Code Generation: Uses riverpod_generator for type-safe providers
- State Notifiers: For mutable state with business logic
- Async Notifiers: For loading and error states
- Stream Providers: For real-time data
Immutable Models: Freezed
- All data models use Freezed for immutability
- Union types for state representation
- Built-in JSON serialization
- CopyWith extensions for updates
Local Storage: Hive
- hive_ce: Community edition of Hive
- Follow the existing model pattern: most stores use hive_ce, while some tracked models still declare @HiveType and @HiveField explicitly
- Type adapters auto-generated
- Persistent key-value storage
Dependency Injection
Services and stores are injected via:
1. Providers: Expose dependencies to UI
2. GetIt: Service location (where applicable)
3. Constructor Injection: Explicit dependencies
Data Flow
User Action → Widget → Provider → Service/Store → Model Update → UI Rebuild
1. User interacts with widget
2. Widget calls provider method
3. Provider updates state via service/store
3. State change triggers UI rebuild
4. New state reflected in widgetCustom Dependencies
The project uses several custom forks to extend functionality:
- dartssh2: Enhanced SSH features
- xterm: Terminal emulator with mobile support
- fl_lib: Shared UI components and utilities
Threading
- Isolates: Heavy computation off main thread
- computer package: Multi-threading utilities
- Async/Await: Non-blocking I/O operations
---
Src/Content/Docs/Development/Building
---
title: Building
description: Build instructions for different platforms
---
Server Box uses a custom build system (fl_build) for cross-platform builds.
Prerequisites
- Flutter SDK (stable channel)
- Platform-specific tools (Xcode for iOS, Android Studio for Android)
- Rust toolchain (for some native dependencies)
Development Build
Run in development mode
flutter run
Run on specific device
flutter run -d <device-id>
Production Build
The project uses fl_build for building:
Build for specific platform
dart run fl_build -p <platform>
Available platforms:
- ios
- android
- macos
- linux
- windows
Platform-Specific Builds
iOS
dart run fl_build -p ios
Requires:
- macOS with Xcode
- CocoaPods
- Apple Developer account for signingAndroid
dart run fl_build -p android
Requires:
- Android SDK
- Java Development Kit
- Keystore for signingmacOS
dart run fl_build -p macos
Linux
dart run fl_build -p linux
Windows
dart run fl_build -p windows
Requires Windows with Visual Studio.Pre/Post Build
The make.dart script handles:
- Metadata generation
- Version string updates
- Platform-specific configurations
Troubleshooting
Clean Build
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub get
Version Mismatch
Ensure all dependencies are compatible:
flutter pub upgrade
Release Checklist
1. Update version in pubspec.yaml
2. Run code generation
3. Run tests
4. Build for all target platforms
5. Test on physical devices
6. Create GitHub release
---
Src/Content/Docs/Development/Codegen
---
title: Code Generation
description: Using build_runner for code generation
---
Server Box heavily uses code generation for models, state management, and serialization.
When to Run Code Generation
Run after modifying:
- Models with @freezed annotation
- Classes with @JsonSerializable
- Hive models
- Providers with @riverpod
- Localizations (ARB files)
Running Code Generation
Generate all code
dart run build_runner build --delete-conflicting-outputs
Clean generated build cache
dart run build_runner clean
Then regenerate
dart run build_runner build --delete-conflicting-outputs
)')" title="Copy section prompt for LLMs"> Copy SectionGenerated Files
Freezed (
.freezed.dart)Immutable data models with union types:
@freezed
class ServerState with _$ServerState {
const factory ServerState.connected() = Connected;
const factory ServerState.disconnected() = Disconnected;
const factory ServerState.error(String message) = Error;
}
)')" title="Copy section prompt for LLMs"> Copy SectionJSON Serialization (
.g.dart)Generated from
json_serializable:
@JsonSerializable()
class Server {
final String id;
final String name;
final String host;
Server({required this.id, required this.name, required this.host});
factory Server.fromJson(Map<String, dynamic> json) =>
_$ServerFromJson(json);
Map<String, dynamic> toJson() => _$ServerToJson(this);
}
)')" title="Copy section prompt for LLMs"> Copy SectionRiverpod Providers (
.g.dart)Generated from
@riverpodannotation:
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() => 0;
}
)')" title="Copy section prompt for LLMs"> Copy SectionHive Adapters (
.g.dart)Auto-generated for Hive models (hive_ce):
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}
Localization Generation
flutter gen-l10n
Generates lib/generated/l10n/ from lib/l10n/*.arb files.Tips
- Use --delete-conflicting-outputs to avoid conflicts
- Keep generated files in version control when they are already tracked by this repository
- Never manually edit generated files
---
Src/Content/Docs/Development/State
---
title: State Management
description: Riverpod-based state management patterns
---
Server Box uses Riverpod with code generation for state management.
Provider Types
StateProvider
Simple state that can be read and written:
@riverpod
class Settings extends _$Settings {
@override
SettingsModel build() {
return SettingsModel.defaults();
}
void update(SettingsModel newSettings) {
state = newSettings;
}
}
AsyncNotifierProvider
State that loads asynchronously with loading/error states:
@riverpod
class ServerStatus extends _$ServerStatus {
@override
Future<StatusModel> build(Server server) async {
return fetchStatus(server);
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => fetchStatus(server));
}
}
StreamProvider
Real-time data from streams:
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}
State Patterns
Loading States
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
Family Providers
Parameterized providers:
@riverpod
List<Container> containers(ContainersRef ref, Server server) {
return containerService.list(server);
}
Auto-Dispose
Providers that dispose when no longer referenced:
@Riverpod(keepAlive: false)
class TempState extends _$TempState {
// ...
}
Best Practices
1. Use code generation: Always use @riverpod annotation
2. Co-locate providers: Place near consuming widgets
3. Avoid singletons: Use providers instead
4. Layer correctly: Keep UI logic separate from business logic
Reading State in Widgets
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}
Modifying State
ref.read(settingsProvider.notifier).update(newSettings);
---Src/Content/Docs/Development/Structure
---
title: Project Structure
description: Understanding the Server Box codebase
---
The Server Box project follows a modular architecture with clear separation of concerns.
Directory Structure
lib/
├── core/ # Core utilities and extensions
├── data/ # Data layer
│ ├── model/ # Data models by feature
│ ├── provider/ # Riverpod providers
│ └── store/ # Local storage (Hive)
├── view/ # UI layer
│ ├── page/ # Main pages
│ └── widget/ # Reusable widgets
├── generated/ # Generated localization
├── l10n/ # Localization ARB files
└── hive/ # Hive adapters
)')" title="Copy chapter prompt for LLMs"> Copy ChapterCore Layer (
lib/core/)Contains utilities, extensions, and routing configuration:
- Extensions: Dart extensions for common types
- Routes: App routing configuration
- Utils: Shared utility functionsData Layer (
lib/data/)Models (
lib/data/model/)Organized by feature:
-
server/- Server connection and status models
-container/- Docker container models
-ssh/- SSH session models
-sftp/- SFTP file models
-app/- App-specific modelsProviders (
lib/data/provider/)Riverpod providers for dependency injection and state management:
- Server providers
- UI state providers
- Service providersStores (
lib/data/store/)Hive-based local storage:
- Server storage
- Settings storage
- Cache storageView Layer (
lib/view/)Pages (
lib/view/page/)Main application screens:
-
server/- Server management pages
-ssh/- SSH terminal pages
-container/- Container pages
-setting/- Settings pages
-storage/- SFTP pages
-snippet/- Snippet pagesWidgets (
lib/view/widget/)Reusable UI components:
- Server cards
- Status charts
- Input components
- DialogsGenerated Files
-
lib/generated/l10n/- Auto-generated localization
-*.g.dart- Generated code (json_serializable, freezed, hive, riverpod)
-*.freezed.dart- Freezed immutable classesPackages Directory (
/packages/)Contains custom forks of dependencies:
-
dartssh2/- SSH library
-xterm/- Terminal emulator
-fl_lib/- Shared utilities
-fl_build/- Build system---
Src/Content/Docs/Development/Testing
---
title: Testing
description: Testing strategies and running tests
---Running Tests
Run all tests
flutter test
Run specific test file
flutter test test/battery_test.dart
Run with coverage
flutter test --coverage
Test Structure
Tests are located in the test/ directory. The current suite is mostly flat and grouped by parser, model, and utility behavior, for example cpu_test.dart, container_test.dart, and ssh_config_test.dart.
Unit Tests
Test business logic and data models:
test('should calculate CPU percentage', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});
Widget Tests
Test UI components:
testWidgets('ServerCard displays server name', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: ServerCard(server: testServer),
),
),
);
expect(find.text('Test Server'), findsOneWidget);
});
Provider Tests
Test Riverpod providers:
test('serverStatusProvider returns status', () async {
final container = ProviderContainer();
final status = await container.read(serverStatusProvider(testServer).future);
expect(status, isA<StatusModel>());
});
External Dependencies
Avoid tests that depend on real SSH servers. Keep parser, model, and command-builder tests deterministic; add targeted fakes or fixtures when a feature introduces a service boundary.
Integration Tests
There is no integration_test/ suite in the current repository. Add integration tests only when a feature needs end-to-end device or app-flow coverage.
Best Practices
1. Arrange-Act-Assert: Structure tests clearly
2. Descriptive names: Test names should describe behavior
3. One assertion per test: Keep tests focused
4. Mock external deps: Don't depend on real servers
5. Test edge cases: Empty lists, null values, etc.
---
Src/Content/Docs/De/Development/Architecture
---
title: Architektur
description: Architekturmuster und Designentscheidungen
---
Server Box folgt den Prinzipien der Clean Architecture mit einer klaren Trennung zwischen Daten-, Domänen- und Präsentationsschicht.
Schichtarchitektur
┌─────────────────────────────────────┐
│ Präsentationsschicht │
│ (lib/view/page/) │
│ - Seiten, Widgets, Controller │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Business-Logik-Schicht │
│ (lib/data/provider/) │
│ - Riverpod Provider │
│ - Zustandsverwaltung │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Datenschicht │
│ (lib/data/model/, store/) │
│ - Modelle, Speicher, Dienste │
└─────────────────────────────────────┘
Schlüsselmuster
Zustandsverwaltung: Riverpod
- Codegenerierung: Verwendet riverpod_generator für typsichere Provider
- State Notifier: Für veränderlichen Zustand mit Business-Logik
- Async Notifier: Für Lade- und Fehlerzustände
- Stream Provider: Für Echtzeitdaten
Unveränderliche Modelle: Freezed
- Alle Datenmodelle verwenden Freezed für Unveränderlichkeit
- Union-Typen zur Darstellung von Zuständen
- Integrierte JSON-Serialisierung
- CopyWith-Erweiterungen für Aktualisierungen
Lokale Speicherung: Hive
- hive_ce: Community-Edition von Hive
- Folgen Sie dem bestehenden Modellmuster: Die meisten Stores verwenden hive_ce, einige verfolgte Modelle deklarieren weiterhin explizit @HiveType und @HiveField
- Typ-Adapter werden automatisch generiert
- Persistenter Key-Value-Speicher
Dependency Injection
Dienste und Stores werden injiziert über:
1. Provider: Stellen Abhängigkeiten der UI zur Verfügung
2. GetIt: Service-Locator (wo anwendbar)
3. Konstruktor-Injektion: Explizite Abhängigkeiten
Datenfluss
Benutzeraktion → Widget → Provider → Dienst/Store → Modell-Update → UI-Neuaufbau
1. Benutzer interagiert mit Widget
2. Widget ruft Provider-Methode auf
3. Provider aktualisiert Zustand über Dienst/Store
4. Zustandsänderung löst Neuaufbau der UI aus
5. Neuer Zustand spiegelt sich im Widget widerEigene Abhängigkeiten
Das Projekt verwendet mehrere eigene Forks zur Funktionserweiterung:
- dartssh2: Erweiterte SSH-Funktionen
- xterm: Terminal-Emulator mit mobiler Unterstützung
- fl_lib: Gemeinsame UI-Komponenten und Dienstprogramme
Threading
- Isolates: Rechenintensive Aufgaben außerhalb des Main-Threads
- computer-Paket: Dienstprogramme für Multi-Threading
- Async/Await: Nicht-blockierende I/O-Operationen
---
Src/Content/Docs/De/Development/Building
---
title: Bauen
description: Bauanleitungen für verschiedene Plattformen
---
Server Box verwendet ein benutzerdefiniertes Build-System (fl_build) für plattformübergreifende Builds.
Voraussetzungen
- Flutter SDK (stabiler Kanal)
- Plattformspezifische Tools (Xcode für iOS, Android Studio für Android)
- Rust-Toolchain (für einige native Abhängigkeiten)
Entwicklungs-Build
Im Entwicklungsmodus ausführen
flutter run
Auf einem bestimmten Gerät ausführen
flutter run -d <device-id>
Produktions-Build
Das Projekt verwendet fl_build zum Bauen:
Für eine bestimmte Plattform bauen
dart run fl_build -p <platform>
Verfügbare Plattformen:
- ios
- android
- macos
- linux
- windows
Plattformspezifische Builds
iOS
dart run fl_build -p ios
Erfordert:
- macOS mit Xcode
- CocoaPods
- Apple Developer Account für die SignierungAndroid
dart run fl_build -p android
Erfordert:
- Android SDK
- Java Development Kit
- Keystore für die SignierungmacOS
dart run fl_build -p macos
Linux
dart run fl_build -p linux
Windows
dart run fl_build -p windows
Erfordert Windows mit Visual Studio.Vor/Nach dem Build
Das Skript make.dart übernimmt:
- Metadaten-Generierung
- Aktualisierung der Versions-Strings
- Plattformspezifische Konfigurationen
Fehlerbehebung
Clean Build
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub get
Versions-Konflikt
Stellen Sie sicher, dass alle Abhängigkeiten kompatibel sind:
flutter pub upgrade
Release-Checkliste
1. Version in pubspec.yaml aktualisieren
2. Codegenerierung ausführen
3. Tests ausführen
4. Für alle Zielplattformen bauen
5. Auf physischen Geräten testen
6. GitHub-Release erstellen
---
Src/Content/Docs/De/Development/Codegen
---
title: Codegenerierung
description: Verwendung von build_runner für die Codegenerierung
---
Server Box verwendet intensiv Codegenerierung für Modelle, Zustandsverwaltung und Serialisierung.
Wann sollte die Codegenerierung ausgeführt werden?
Führen Sie sie aus nach der Änderung von:
- Modellen mit @freezed Annotation
- Klassen mit @JsonSerializable
- Hive-Modellen
- Providern mit @riverpod
- Lokalisierungen (ARB-Dateien)
Codegenerierung ausführen
Gesamten Code generieren
dart run build_runner build --delete-conflicting-outputs
Generierten Build-Cache bereinigen
dart run build_runner clean
Dann neu generieren
dart run build_runner build --delete-conflicting-outputs
)')" title="Copy section prompt for LLMs"> Copy SectionGenerierte Dateien
Freezed (
.freezed.dart)Unveränderliche Datenmodelle mit Union Types:
@freezed
class ServerState with _$ServerState {
const factory ServerState.connected() = Connected;
const factory ServerState.disconnected() = Disconnected;
const factory ServerState.error(String message) = Error;
}
)')" title="Copy section prompt for LLMs"> Copy SectionJSON-Serialisierung (
.g.dart)Generiert durch
json_serializable:
@JsonSerializable()
class Server {
final String id;
final String name;
final String host;
Server({required this.id, required this.name, required this.host});
factory Server.fromJson(Map<String, dynamic> json) =>
_$ServerFromJson(json);
Map<String, dynamic> toJson() => _$ServerToJson(this);
}
)')" title="Copy section prompt for LLMs"> Copy SectionRiverpod Provider (
.g.dart)Generiert aus der
@riverpodAnnotation:
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() => 0;
}
)')" title="Copy section prompt for LLMs"> Copy SectionHive-Adapter (
.g.dart)Automatisch generiert für Hive-Modelle (hive_ce):
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}
Generierung der Lokalisierung
flutter gen-l10n
Generiert lib/generated/l10n/ aus lib/l10n/*.arb Dateien.Tipps
- Verwenden Sie --delete-conflicting-outputs, um Konflikte zu vermeiden.
- Behalten Sie generierte Dateien in der Versionsverwaltung, wenn dieses Repository sie bereits verfolgt.
- Bearbeiten Sie generierte Dateien niemals manuell.
---
Src/Content/Docs/De/Development/State
---
title: Zustandsverwaltung
description: Riverpod-basierte Zustandsverwaltungsmuster
---
Server Box verwendet Riverpod mit Codegenerierung für die Zustandsverwaltung.
Provider-Typen
StateProvider
Einfacher Zustand, der gelesen und geschrieben werden kann:
@riverpod
class Settings extends _$Settings {
@override
SettingsModel build() {
return SettingsModel.defaults();
}
void update(SettingsModel newSettings) {
state = newSettings;
}
}
AsyncNotifierProvider
Zustand, der asynchron mit Lade-/Fehlerzuständen geladen wird:
@riverpod
class ServerStatus extends _$ServerStatus {
@override
Future<StatusModel> build(Server server) async {
return fetchStatus(server);
}
Future<void> refresh() async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() => fetchStatus(server));
}
}
StreamProvider
Echtzeitdaten aus Streams:
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}
Zustandsmuster
Ladezustände
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
Family Provider
Parametrisierte Provider:
@riverpod
List<Container> containers(ContainersRef ref, Server server) {
return containerService.list(server);
}
Auto-Dispose
Provider, die verworfen werden, wenn sie nicht mehr referenziert werden:
@Riverpod(keepAlive: false)
class TempState extends _$TempState {
// ...
}
Best Practices
1. Codegenerierung nutzen: Immer die @riverpod Annotation verwenden.
2. Provider lokal platzieren: In der Nähe der Widgets platzieren, die sie nutzen.
3. Singletons vermeiden: Stattdessen Provider verwenden.
4. Korrekt schichten: UI-Logik von Business-Logik getrennt halten.
Zustand in Widgets lesen
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}
Zustand ändern
ref.read(settingsProvider.notifier).update(newSettings);
---Src/Content/Docs/De/Development/Structure
---
title: Projektstruktur
description: Verständnis der Server Box Codebasis
---
Das Server Box-Projekt folgt einer modularen Architektur mit einer klaren Trennung der Belange.
Verzeichnisstruktur
lib/
├── core/ # Kern-Dienstprogramme und Erweiterungen
├── data/ # Datenschicht
│ ├── model/ # Datenmodelle nach Funktionen
│ ├── provider/ # Riverpod Provider
│ └── store/ # Lokale Speicherung (Hive)
├── view/ # UI-Schicht
│ ├── page/ # Hauptseiten
│ └── widget/ # Wiederverwendbare Widgets
├── generated/ # Generierte Lokalisierung
├── l10n/ # Lokalisierungs-ARB-Dateien
└── hive/ # Hive-Adapter
)')" title="Copy chapter prompt for LLMs"> Copy ChapterKernschicht (
lib/core/)Enthält Dienstprogramme, Erweiterungen und Routing-Konfiguration:
- Erweiterungen: Dart-Erweiterungen für gängige Typen
- Routen: App-Routing-Konfiguration
- Dienstprogramme: Gemeinsame HilfsfunktionenDatenschicht (
lib/data/)Modelle (
lib/data/model/)Organisiert nach Funktionen:
-
server/- Server-Verbindung und Status-Modelle
-container/- Docker-Container-Modelle
-ssh/- SSH-Sitzungs-Modelle
-sftp/- SFTP-Datei-Modelle
-app/- App-spezifische ModelleProvider (
lib/data/provider/)Riverpod Provider für Dependency Injection und Zustandsverwaltung:
- Server Provider
- UI-Zustands-Provider
- Service ProviderStores (
lib/data/store/)Hive-basierte lokale Speicherung:
- Server-Speicher
- Einstellungs-Speicher
- Cache-SpeicherUI-Schicht (
lib/view/)Seiten (
lib/view/page/)Hauptbildschirme der Anwendung:
-
server/- Server-Verwaltungsseiten
-ssh/- SSH-Terminal-Seiten
-container/- Container-Seiten
-setting/- Einstellungsseiten
-storage/- SFTP-Seiten
-snippet/- Snippet-SeitenWidgets (
lib/view/widget/)Wiederverwendbare UI-Komponenten:
- Server-Karten
- Status-Diagramme
- Eingabe-Komponenten
- DialogeGenerierte Dateien
-
lib/generated/l10n/- Automatisch generierte Lokalisierung
-*.g.dart- Generierter Code (json_serializable, freezed, hive, riverpod)
-*.freezed.dart- Unveränderliche Freezed-KlassenVerzeichnis "packages" (
/packages/)Enthält eigene Forks von Abhängigkeiten:
-
dartssh2/- SSH-Bibliothek
-xterm/- Terminal-Emulator
-fl_lib/- Gemeinsame Dienstprogramme
-fl_build/- Build-System---
Src/Content/Docs/De/Development/Testing
---
title: Testen
description: Teststrategien und Ausführung von Tests
---Tests ausführen
Alle Tests ausführen
flutter test
Bestimmte Testdatei ausführen
flutter test test/battery_test.dart
Mit Coverage ausführen
flutter test --coverage
Teststruktur
Tests befinden sich im Verzeichnis test/. Die aktuelle Suite ist überwiegend flach und nach Parser-, Modell- und Utility-Verhalten gruppiert, zum Beispiel cpu_test.dart, container_test.dart und ssh_config_test.dart.
Unit-Tests
Geschäftslogik und Datenmodelle testen:
test('sollte CPU-Prozentsatz berechnen', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});
Widget-Tests
UI-Komponenten testen:
testWidgets('ServerCard zeigt Servernamen an', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: ServerCard(server: testServer),
),
),
);
expect(find.text('Test Server'), findsOneWidget);
});
Provider-Tests
Riverpod Provider testen:
test('serverStatusProvider gibt Status zurück', () async {
final container = ProviderContainer();
final status = await container.read(serverStatusProvider(testServer).future);
expect(status, isA<StatusModel>());
});
Externe Abhängigkeiten
Vermeiden Sie Tests, die von echten SSH-Servern abhängen. Parser-, Modell- und Command-Builder-Tests sollten deterministisch bleiben; fügen Sie gezielte Fakes oder Fixtures hinzu, wenn eine Funktion eine Service-Grenze einführt.
Integrationstests
Im aktuellen Repository gibt es keine integration_test/-Suite. Fügen Sie Integrationstests nur hinzu, wenn eine Funktion End-to-End-Geräte- oder App-Flow-Abdeckung benötigt.dart
testWidgets('Server hinzufügen Ablauf', (tester) async {
await tester.pumpWidget(MyApp());
// Hinzufügen-Button tippen
await tester.tap(find.byIcon(Icons.add));
await tester.pumpAndSettle();
// Formular ausfüllen
await tester.enterText(find.byKey(Key('name')), 'Test Server');
// ...
});
Best Practices
1. Arrange-Act-Assert: Tests klar strukturieren
2. Beschreibende Namen: Testnamen sollten das Verhalten beschreiben
3. Eine Assertion pro Test: Tests fokussiert halten
4. Externe Abhängigkeiten mocken: Nicht von echten Servern abhängig sein
5. Grenzfälle testen: Leere Listen, Null-Werte, usw.
---
Src/Content/Docs/De/Advanced/Bulk Import
---
title: Massenimport von Servern
description: Importieren Sie mehrere Server aus einer JSON-Datei
---
Importieren Sie mehrere Serverkonfigurationen gleichzeitig mithilfe einer JSON-Datei.
JSON-Format
:::danger[Sicherheitswarnung]
Speichern Sie niemals Klartext-Passwörter in Dateien! Dieses JSON-Beispiel zeigt ein Passwort-Feld nur zur Demonstration, aber Sie sollten:
- SSH-Schlüssel bevorzugen (pubKeyId) anstelle von pwd - diese sind sicherer
- Passwort-Manager oder Umgebungsvariablen verwenden, wenn Sie Passwörter verwenden müssen
- Löschen Sie die Datei sofort nach dem Import - lassen Sie keine Anmeldedaten herumliegen
- Fügen Sie sie zur .gitignore hinzu - checken Sie niemals Anmeldedatendateien in die Versionsverwaltung ein
:::
[
{
"name": "Mein Server",
"ip": "example.com",
"port": 22,
"user": "root",
"pwd": "password",
"pubKeyId": "",
"tags": ["production"],
"autoConnect": false
}
]Felder
| Feld | Erforderlich | Beschreibung |
|-------|----------|-------------|
| name | Ja | Anzeigename |
| ip | Ja | Domain oder IP-Adresse |
| port | Ja | SSH-Port (normalerweise 22) |
| user | Ja | SSH-Benutzername |
| pwd | Nein | Passwort (vermeiden - stattdessen SSH-Schlüssel verwenden) |
| pubKeyId | Nein | Private-Key-ID (aus Private Keys - empfohlen) |
| tags | Nein | Organisations-Tags |
| autoConnect | Nein | Automatische Verbindung beim Start |
Import-Schritte
1. Erstellen Sie eine JSON-Datei mit Serverkonfigurationen
2. Einstellungen → Backup → Server massenhaft importieren
3. Wählen Sie Ihre JSON-Datei aus
4. Bestätigen Sie den Import
Beispiel
[
{
"name": "Produktion",
"ip": "prod.example.com",
"port": 22,
"user": "admin",
"pubKeyId": "my-key",
"tags": ["production", "web"]
},
{
"name": "Entwicklung",
"ip": "dev.example.com",
"port": 2222,
"user": "dev",
"pubKeyId": "dev-key",
"tags": ["development"]
}
]Tipps
- Verwenden Sie SSH-Schlüssel anstelle von Passwörtern, wann immer möglich
- Testen Sie die Verbindung nach dem Import
- Organisieren Sie mit Tags für eine einfachere Verwaltung
- Löschen Sie die JSON-Datei nach dem Import
- Checken Sie niemals JSON-Dateien mit Anmeldedaten in die Versionsverwaltung ein
---
Src/Content/Docs/De/Advanced/Custom Commands
---
title: Benutzerdefinierte Befehle
description: Anzeige der Ausgabe benutzerdefinierter Befehle auf der Serverseite
---
Fügen Sie benutzerdefinierte Shell-Befehle hinzu, um deren Ausgabe auf der Server-Detailseite anzuzeigen.
Einrichtung
1. Servereinstellungen → Benutzerdefinierte Befehle
2. Befehle im JSON-Format eingeben
Basisformat
{
"Anzeigename": "Shell-Befehl"
}Beispiel:
{
"Speicher": "free -h",
"Festplatte": "df -h",
"Laufzeit": "uptime"
}Ergebnisse anzeigen
Nach der Einrichtung erscheinen benutzerdefinierte Befehle auf der Server-Detailseite und werden automatisch aktualisiert.
Spezielle Befehlsnamen
server_card_top_right
Anzeige auf der Serverkarte der Startseite (oben rechts):
{
"server_card_top_right": "Ihr-Befehl-hier"
}Tipps
Absolute Pfade verwenden:
{"Mein Skript": "/usr/local/bin/mein-skript.sh"}Pipe-Befehle:
{"Top-Prozess": "ps aux | sort -rk 3 | head -5"}Ausgabe formatieren:
{"CPU-Last": "uptime | awk -F'load average:' '{print $2}'"}Befehle schnell halten: Unter 5 Sekunden für das beste Erlebnis.
Ausgabe begrenzen:
{"Logs": "tail -20 /var/log/syslog"}Sicherheit
Befehle werden mit den Berechtigungen des SSH-Benutzers ausgeführt. Vermeiden Sie Befehle, die den Systemzustand ändern.
---