flutter_server_box

GitHub

ServerBox - server status & toolbox

AI Prompts & Endpoints
Agent Skills View CodeWiki Knowledge Base

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

text
┌─────────────────────────────────────┐
│ 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

text
Action Utilisateur → Widget → Provider → Service/Store → Mise à jour Modèle → Reconstruction UI

1. 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

bash

Exécuter en mode développement


flutter run

Exécuter sur un appareil spécifique


flutter run -d <id-appareil>

Construction pour la production

Le projet utilise fl_build pour la construction :

bash

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

bash
dart run fl_build -p ios

Nécessite :
- macOS avec Xcode
- CocoaPods
- Compte Apple Developer pour la signature

Android

bash
dart run fl_build -p android

Nécessite :
- Android SDK
- Java Development Kit
- Keystore pour la signature

macOS

bash
dart run fl_build -p macos

Linux

bash
dart run fl_build -p linux

Windows

bash
dart run fl_build -p windows

Né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)

bash
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub get

Incompatibilité de version

Assurez-vous que toutes les dépendances sont compatibles :

bash
flutter pub upgrade

Liste 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

bash

Générer tout le code


dart run build_runner build --delete-conflicting-outputs

Nettoyer le cache de génération


dart run build_runner clean

Puis régénérer


dart run build_runner build --delete-conflicting-outputs

Fichiers générés

Freezed (.freezed.dart)

Modèles de données immuables avec types Union :

dart
@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 :

dart
@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 :

dart
@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) :

dart
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}

Génération de localisation

bash
flutter gen-l10n

Gé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 :

dart
@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 :

dart
@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) :

dart
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}

Modèles d'état

États de chargement

dart
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)

Family Providers

Providers paramétrés :

dart
@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 :

dart
@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

dart
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}

Modifier l'état

dart
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

text
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 Hive

Couche 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

bash

Exécuter tous les tests


flutter test

Exécuter un fichier de test spécifique


flutter test test/battery_test.dart

Exécuter avec couverture de code


flutter test --coverage

Structure 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 :

dart
test('devrait calculer le pourcentage du CPU', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});

Tests de widgets

Tester les composants UI :

dart
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 :

dart
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');
// ...
});

text

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
:::

json
[
{
"name": "Mon serveur",
"ip": "example.com",
"port": 22,
"user": "root",
"pwd": "password",
"pubKeyId": "",
"tags": ["production"],
"autoConnect": false
}
]
text

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

json
[
{
"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"]
}
]
text

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

json
{
"Nom d'affichage": "commande shell"
}
text
Exemple :
json
{
"Mémoire": "free -h",
"Disque": "df -h",
"Uptime": "uptime"
}
text

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) :

json
{
"server_card_top_right": "votre-commande-ici"
}
text

Conseils

Utilisez des chemins absolus :

json
{"Mon script": "/usr/local/bin/mon-script.sh"}
text
Commandes avec pipe :
json
{"Processus principal": "ps aux | sort -rk 3 | head -5"}
text
Formater la sortie :
json
{"Charge CPU": "uptime | awk -F'load average:' '{print $2}'"}
text
Gardez les commandes rapides : Moins de 5 secondes pour une meilleure expérience.

Limiter la sortie :

json
{"Logs": "tail -20 /var/log/syslog"}
text

Sécurité

Les commandes s'exécutent avec les permissions de l'utilisateur SSH. Évitez les commandes qui modifient l'état du système.

---

---
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
text
Devient : debian.png, ubuntu.png, arch.png, etc.

{BRIGHT} - Thème

Remplacé automatiquement par le thème actuel :


https://example.com/{BRIGHT}.png
text
Devient : light.png ou dark.png

Combiner les deux


https://example.com/{DIST}-{BRIGHT}.png
text
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.

json
{"timeOut": 10}
text
Type : entier | Par défaut : 5 | Plage : 1-60

recordHistory

Enregistrer l'historique (chemins SFTP, etc.).

json
{"recordHistory": true}
text
Type : booléen | Par défaut : true

textFactor

Facteur de mise à l'échelle du texte.

json
{"textFactor": 1.2}
text
Type : double | Par défaut : 1.0 | Plage : 0.8-1.5

Trouver plus de paramètres

Tous les paramètres sont définis dans setting.dart.

Recherchez :

dart
late final settingName = StoreProperty(box, 'settingKey', defaultValue);
text

⚠️ 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 :

bash
# /etc/ssh/sshd_config
ClientAliveInterval 60
ClientAliveCountMax 3
text
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-plan

Problè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
text
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 iOSApplication 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.) │
└─────────────────────────────────────────────────┘
text

Fondations de l'application

Point d'entrée principal

lib/main.dart initialise l'application :

dart
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
text

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
text
1. Pré-construction : Calculer la version à partir de Git
2. Construction : Compiler pour la plateforme cible
3. Post-construction : Paqueter et signer

Exemple 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
text

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
text

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 │
└─────────────────────────────────────────────┘
text

Établissement de la connexion

Création du client SFTP

dart
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;
}

text

Réutilisation de la connexion

SFTP réutilise les connexions SSH existantes :

dart
class ServerProvider {
SSHClient? _sshClient;
SftpClient? _sftpClient;

Future<SftpClient> getSftpClient(String spiId) async {
_sftpClient ??= await _sshClient!.openSftp();
return _sftpClient!;
}
}

text

Opérations du système de fichiers

Liste des répertoires

dart
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;
}

text

Métadonnées de fichiers

dart
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);
}

text

Opérations sur les fichiers

Téléversement (Upload)

dart
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);
}

text

Téléchargement (Download)

dart
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();
}

text

Édition des permissions

dart
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"');
}

text

Gestion des chemins

Structure de chemin

dart
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,
);
}
}

text

Historique de navigation

dart
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;
}
}

text

Système de transfert

Requête de transfert

dart
class SftpReq {
final Spi spi;
final String remotePath;
final String localPath;
final SftpReqType type;
final DateTime createdAt;

int? totalBytes;
int? transferredBytes;
String? error;
}

text

Suivi de progression

dart
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);
}
}

text

Gestion de la file d'attente

dart
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);
}
}
}

text

Modèle de stockage local

Cache de téléchargement

Fichiers téléchargés stockés sur :

dart
String getLocalDownloadPath(String spiId, String remotePath) {
final normalized = remotePath.replaceAll('/', '_');
return 'Paths.file/$spiId/$normalized';
}
text
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

dart
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();
}

text

Intégration d'un éditeur externe

dart
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
}

text

Gestion des erreurs

Erreurs de permission

dart
try {
await sftp.upload(...);
} on SftpPermissionException {
showError('Permission refusée : ${stat.path}');
showHint('Vérifiez les permissions et la propriété du fichier');
}
text

Erreurs de connexion

dart
try {
await sftp.listDir(path);
} on SftpConnectionException {
showError('Connexion perdue');
await reconnect();
}
text

Erreurs d'espace disque

dart
try {
await sftp.upload(...);
} on SftpNoSpaceException {
showError('Disque plein sur le serveur distant');
}
text

Optimisations de performance

Cache de répertoire

dart
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;
}
}

text

Chargement différé (Lazy Loading)

Pour les répertoires volumineux (>1000 éléments) :

dart
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));
}
text

Pagination

dart
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,
);
}
}

text
---

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

text
Entrée utilisateur → Configuration Spi → genClient() → Client SSH → Session
text

Étape 1 : Configuration

Le modèle Spi (Server Parameter Info) contient :

dart
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
}
text

Étape 2 : Génération du client

genClient(spi) crée le client SSH :

dart
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;
}

text

Étape 3 : Serveur de rebond (si configuré)

Pour les serveurs de rebond, connexion récursive :

dart
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é
}
text

Méthodes d'authentification

Authentification par mot de passe

dart
onPasswordRequest: () => spi.pwd
text
- Mot de passe stocké chiffré dans Hive
- Déchiffré lors de la connexion
- Envoyé au serveur pour vérification

Authentification par clé privée

dart
onIdentityRequest: () async {
final key = await KeyStore.get(spi.keyId);
return decyptPem(key.pem, key.password);
}
text
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'authentification

Keyboard-Interactive

dart
onUserInfoRequest: (instructions) async {
// Gérer le challenge-response
return responses;
}
text
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

text
{spi.id}::{keyType}
text
Exemple :
text
mon-serveur::ssh-ed25519
mon-serveur::ecdsa-sha2-nistp256
text

Formats d'empreinte

MD5 Hex :

text
aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99
text
Base64 :
text
SHA256:AbCdEf1234567890...=
text

Flux de vérification

dart
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',
);
}
}

text

Gestion des sessions

Mise en commun des connexions (Pooling)

Clients actifs maintenus dans ServerProvider :

dart
class ServerProvider {
final Map<String, SSHClient> _clients = {};

SSHClient getClient(String spiId) {
return _clients[spiId] ??= connect(spiId);
}
}

text

Keep-Alive

Maintenir la connexion pendant l'inactivité :

dart
Timer.periodic(
Duration(seconds: 30),
(_) => client.sendKeepAlive(),
);
text

Reconnexion automatique

En cas de perte de connexion :

dart
client.onError.listen((error) async {
await Future.delayed(Duration(seconds: 5));
reconnect();
});
text

Cycle de vie de la connexion

text
┌─────────────┐
│ Initial │
└──────┬──────┘
│ connect()

┌─────────────┐
│ Connexion │ ←──┐
└──────┬──────┘ │
│ succès │
↓ │ échec (retry)
┌─────────────┐ │
│ Connecté │───┘
└──────┬──────┘


┌─────────────┐
│ Actif │ ──→ Envoyer des commandes
└──────┬──────┘

↓ (erreur/déconnexion)
┌─────────────┐
│ Déconnecté │
└─────────────┘
text

Gestion des erreurs

Délai d'attente de connexion (Timeout)

dart
try {
await client.connect().timeout(
Duration(seconds: 30),
);
} on TimeoutException {
throw ConnectionException('Délai d\'attente de connexion dépassé');
}
text

Échec d'authentification

dart
onAuthFail: (error) {
if (error.contains('password')) {
return 'Mot de passe invalide';
} else if (error.contains('key')) {
return 'Clé SSH invalide';
}
return 'Authentification échouée';
}
text

Discordance de clé d'hôte

dart
onHostKeyMismatch: (stored, current) {
showSecurityWarning(
'La clé d\'hôte a changé !',
'Attaque MITM possible',
);
}
text

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 │
└─────────────────────────────────────────────┘
text

Types de Providers utilisés

1. StateProvider (État simple)

Pour un état simple et observable :

dart
@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
}
}

text
Utilisation :
dart
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themeNotifierProvider);
return Text('Thème : $theme');
}
}
text

2. AsyncNotifierProvider (État asynchrone)

Pour les données qui se chargent de manière asynchrone :

dart
@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);
});
}
}

text
Utilisation :
dart
final status = ref.watch(serverStatusProvider(server));

status.when(
data: (data) => StatusWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)

text

3. StreamProvider (Données en temps réel)

Pour les flux de données continus :

dart
@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;
}

text
Utilisation :
dart
final cpu = ref.watch(cpuUsageProvider(server));

cpu.when(
data: (usage) => CpuChart(usage),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
)

text

4. Family Providers (Paramétrés)

Providers qui acceptent des paramètres :

dart
@riverpod
Future<List<Container>> containers(ContainersRef ref, Server server) async {
final client = await ref.watch(sshClientProvider(server).future);
return await client.listContainers();
}
text
Utilisation :
dart
final containers = ref.watch(containersProvider(server));

// Différents serveurs = différents états mis en cache
final containers2 = ref.watch(containersProvider(server2));

text

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 │
└─────────────────────────────────────────────┘
text

Cycle de vie d'une session de terminal

1. Création de la session

dart
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,
);
}

text

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
text

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

dart
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());
}
}

text

Pincer pour zoomer (Pinch-to-Zoom)

dart
GestureDetector(
onScaleStart: () => _baseFontSize = currentFontSize,
onScaleUpdate: (details) {
final newFontSize = _baseFontSize * details.scale;
resize(newFontSize);
},
)
text

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 :

sh
brew install --cask server-box
text
Caractéristiques :
- Intégration native de la barre de menus
- Prise en charge d'Intel et d'Apple Silicon

Linux

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 │
└─────────────────────────────────────┘
text

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
text
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 widget

Dependencias 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

bash

Ejecutar en modo desarrollo


flutter run

Ejecutar en un dispositivo específico


flutter run -d <id-del-dispositivo>
text

Compilación de Producción

El proyecto utiliza fl_build para compilar:

bash

Compilar para una plataforma específica


dart run fl_build -p <plataforma>

Plataformas disponibles:


- ios


- android


- macos


- linux


- windows


text

Compilaciones Específicas por Plataforma

iOS

bash
dart run fl_build -p ios
text
Requiere:
- macOS con Xcode
- CocoaPods
- Cuenta de Apple Developer para la firma

Android

bash
dart run fl_build -p android
text
Requiere:
- Android SDK
- Java Development Kit
- Keystore para la firma

macOS

bash
dart run fl_build -p macos
text

Linux

bash
dart run fl_build -p linux
text

Windows

bash
dart run fl_build -p windows
text
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

bash
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub get
text

Discrepancia de Versión

Asegúrate de que todas las dependencias son compatibles:

bash
flutter pub upgrade
text

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

bash

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
text

Archivos Generados

Freezed (.freezed.dart)

Modelos de datos inmutables con tipos Union:

dart
@freezed
class ServerState with _$ServerState {
const factory ServerState.connected() = Connected;
const factory ServerState.disconnected() = Disconnected;
const factory ServerState.error(String message) = Error;
}
text

Serialización JSON (.g.dart)

Generado por json_serializable:

dart
@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);
}

text

Providers de Riverpod (.g.dart)

Generados a partir de la anotación @riverpod:

dart
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() => 0;
}
text

Adaptadores de Hive (.g.dart)

Auto-generados para modelos de Hive (hive_ce):

dart
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}
text

Generación de Localización

bash
flutter gen-l10n
text
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:

dart
@riverpod
class Settings extends _$Settings {
@override
SettingsModel build() {
return SettingsModel.defaults();
}

void update(SettingsModel newSettings) {
state = newSettings;
}
}

text

AsyncNotifierProvider

Estado que se carga de forma asíncrona con estados de carga/error:

dart
@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));
}
}

text

StreamProvider

Datos en tiempo real desde flujos (streams):

dart
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}
text

Patrones de Estado

Estados de Carga

dart
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
text

Family Providers

Providers parametrizados:

dart
@riverpod
List<Container> containers(ContainersRef ref, Server server) {
return containerService.list(server);
}
text

Auto-Dispose

Providers que se eliminan cuando ya no están referenciados:

dart
@Riverpod(keepAlive: false)
class TempState extends _$TempState {
// ...
}
text

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

dart
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}
text

Modificar el Estado

dart
ref.read(settingsProvider.notifier).update(newSettings);
text
---

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
text

Capa 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 compartidas

Capa 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 app

Providers (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 servicios

Almacenes (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álogos

Archivos Generados

- lib/generated/l10n/ - Localización auto-generada
- *.g.dart - Código generado (json_serializable, freezed, hive, riverpod)
- *.freezed.dart - Clases inmutables de Freezed

Directorio 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

bash

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
text

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:

dart
test('debería calcular el porcentaje de CPU', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});
text

Pruebas de Widgets

Probar componentes de la interfaz de usuario (UI):

dart
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);
});

text

Pruebas de Providers

Probar providers de Riverpod:

dart
test('serverStatusProvider devuelve el estado', () async {
final container = ProviderContainer();
final status = await container.read(serverStatusProvider(testServer).future);
expect(status, isA<StatusModel>());
});
text

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
:::

json
[
{
"name": "Mi Servidor",
"ip": "example.com",
"port": 22,
"user": "root",
"pwd": "password",
"pubKeyId": "",
"tags": ["production"],
"autoConnect": false
}
]
text

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

json
[
{
"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"]
}
]
text

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

json
{
"Nombre a mostrar": "comando shell"
}
text
Ejemplo:
json
{
"Memoria": "free -h",
"Disco": "df -h",
"Tiempo de actividad": "uptime"
}
text

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):

json
{
"server_card_top_right": "tu-comando-aquí"
}
text

Consejos

Usa rutas absolutas:

json
{"Mi Script": "/usr/local/bin/mi-script.sh"}
text
Comandos con tuberías (pipes):
json
{"Proceso principal": "ps aux | sort -rk 3 | head -5"}
text
Formatear salida:
json
{"Carga de CPU": "uptime | awk -F'load average:' '{print $2}'"}
text
Mantén los comandos rápidos: Menos de 5 segundos para una mejor experiencia.

Limitar salida:

json
{"Logs": "tail -20 /var/log/syslog"}
text

Seguridad

Los comandos se ejecutan con los permisos del usuario SSH. Evita comandos que modifiquen el estado del sistema.

---

---
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
text
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
text
Se convierte en: light.png o dark.png

Combinar ambos


https://ejemplo.com/{DIST}-{BRIGHT}.png
text
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.

json
{"timeOut": 10}
text
Tipo: entero | Predeterminado: 5 | Rango: 1-60

recordHistory

Guardar historial (rutas SFTP, etc.).

json
{"recordHistory": true}
text
Tipo: booleano | Predeterminado: true

textFactor

Factor de escala de texto.

json
{"textFactor": 1.2}
text
Tipo: doble | Predeterminado: 1.0 | Rango: 0.8-1.5

Encontrar Más Ajustes

Todos los ajustes están definidos en setting.dart.

Busca:

dart
late final settingName = StoreProperty(box, 'settingKey', defaultValue);
text

⚠️ 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:

bash
# /etc/ssh/sshd_config
ClientAliveInterval 60
ClientAliveCountMax 3
text
2. Desactivar optimización de batería:
- MIUI: Batería → "Sin restricciones"
- Android: Ajustes → Aplicaciones → Desactivar optimización
- iOS: Activar actualización en segundo plano

Problemas 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
text
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 iOSApp 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.) │
└─────────────────────────────────────────────────┘
text

Fundamentos de la Aplicación

Punto de Entrada Principal

lib/main.dart inicializa la aplicación:

dart
void main() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
text

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
text
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 firma

Ejemplo 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
text

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
text

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 │
└─────────────────────────────────────────────┘
text

Establecimiento de la Conexión

Creación del Cliente SFTP

dart
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;
}

text

Reutilización de Conexiones

SFTP reutiliza las conexiones SSH existentes:

dart
class ServerProvider {
SSHClient? _sshClient;
SftpClient? _sftpClient;

Future<SftpClient> getSftpClient(String spiId) async {
_sftpClient ??= await _sshClient!.openSftp();
return _sftpClient!;
}
}

text

Operaciones del Sistema de Archivos

Listado de Directorios

dart
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;
}

text

Metadatos de Archivo

dart
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);
}

text

Operaciones de Archivo

Subida (Upload)

dart
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);
}

text

Descarga (Download)

dart
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();
}

text

Edición de Permisos

dart
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"');
}

text

Gestión de Rutas

Estructura de Rutas

dart
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,
);
}
}

text

Historial de Navegación

dart
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;
}
}

text

Sistema de Transferencia

Petición de Transferencia

dart
class SftpReq {
final Spi spi;
final String remotePath;
final String localPath;
final SftpReqType type;
final DateTime createdAt;

int? totalBytes;
int? transferredBytes;
String? error;
}

text

Seguimiento de Progreso

dart
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);
}
}

text

Gestión de Colas

dart
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);
}
}
}

text

Patrón de Almacenamiento Local

Caché de Descargas

Los archivos descargados se guardan en:

dart
String getLocalDownloadPath(String spiId, String remotePath) {
final normalized = remotePath.replaceAll('/', '_');
return 'Paths.file/$spiId/$normalized';
}
text
Ejemplo:
- Remoto: /var/log/nginx/access.log
- spiId: server-123
- Local: Paths.file/server-123/_var_log_nginx_access.log

Edición de Archivos

Flujo de Trabajo de Edición

dart
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();
}

text

Integración con Editor Externo

dart
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
}

text

Gestión de Errores

Errores de Permiso

dart
try {
await sftp.upload(...);
} on SftpPermissionException {
showError('Permiso denegado: ${stat.path}');
showHint('Comprueba los permisos y la propiedad del archivo');
}
text

Erreores de Conexión

dart
try {
await sftp.listDir(path);
} on SftpConnectionException {
showError('Conexión perdida');
await reconnect();
}
text

Errores de Espacio

dart
try {
await sftp.upload(...);
} on SftpNoSpaceException {
showError('Disco lleno en el servidor remoto');
}
text

Optimizaciones de Rendimiento

Caché de Directorios

dart
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;
}
}

text

Carga Perezosa (Lazy Loading)

Para directorios grandes (>1000 elementos):

dart
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));
}
text

Paginación

dart
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,
);
}
}

text
---

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

text
Entrada de Usuario → Configuración Spi → genClient() → Cliente SSH → Sesión
text

Paso 1: Configuración

El modelo Spi (Server Parameter Info) contiene:

dart
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
}
text

Paso 2: Generación del Cliente

genClient(spi) crea el cliente SSH:

dart
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;
}

text

Paso 3: Servidor de Salto (si está configurado)

Para servidores de salto, conexión recursiva:

dart
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
}
text

Métodos de Autenticación

Autenticación por Contraseña

dart
onPasswordRequest: () => spi.pwd
text
- Contraseña almacenada cifrada en Hive
- Descifrada al conectar
- Enviada al servidor para verificación

Autenticación por Clave Privada

dart
onIdentityRequest: () async {
final key = await KeyStore.get(spi.keyId);
return decyptPem(key.pem, key.password);
}
text
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ón

Interacción por Teclado (Keyboard-Interactive)

dart
onUserInfoRequest: (instructions) async {
// Gestionar desafío-respuesta
return responses;
}
text
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

text
{spi.id}::{keyType}
text
Ejemplo:
text
mi-servidor::ssh-ed25519
mi-servidor::ecdsa-sha2-nistp256
text

Formatos de Huella Digital (Fingerprint)

MD5 Hex:

text
aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99
text
Base64:
text
SHA256:AbCdEf1234567890...=
text

Flujo de Verificación

dart
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',
);
}
}

text

Gestión de Sesiones

Pool de Conexiones

Clientes activos mantenidos en ServerProvider:

dart
class ServerProvider {
final Map<String, SSHClient> _clients = {};

SSHClient getClient(String spiId) {
return _clients[spiId] ??= connect(spiId);
}
}

text

Keep-Alive

Mantener la conexión durante la inactividad:

dart
Timer.periodic(
Duration(seconds: 30),
(_) => client.sendKeepAlive(),
);
text

Reconexión Automática

Al perder la conexión:

dart
client.onError.listen((error) async {
await Future.delayed(Duration(seconds: 5));
reconnect();
});
text

Ciclo de Vida de la Conexión

text
┌─────────────┐
│ Inicial │
└──────┬──────┘
│ connect()

┌─────────────┐
│ Conectando │ ←──┐
└──────┬──────┘ │
│ éxito │
↓ │ fallo (reintento)
┌─────────────┐ │
│ Conectado │───┘
└──────┬──────┘


┌─────────────┐
│ Activo │ ──→ Enviar comandos
└──────┬──────┘

↓ (error/desconexión)
┌─────────────┐
│ Desconectado│
└─────────────┘
text

Gestión de Errores

Tiempo de Espera Agotado (Timeout)

dart
try {
await client.connect().timeout(
Duration(seconds: 30),
);
} on TimeoutException {
throw ConnectionException('Tiempo de espera de conexión agotado');
}
text

Fallo de Autenticación

dart
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';
}
text

Discrepancia en Clave de Host

dart
onHostKeyMismatch: (stored, current) {
showSecurityWarning(
'¡La clave de host ha cambiado!',
'Posible ataque MITM',
);
}
text

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 │
└─────────────────────────────────────────────┘
text

Tipos de Provider Utilizados

1. StateProvider (Estado Simple)

Para estados simples y observables:

dart
@riverpod
class ThemeNotifier extends _$ThemeNotifier {
@override
ThemeMode build() {
// Cargar desde ajustes
return SettingStore.themeMode;
}

void setTheme(ThemeMode mode) {
state = mode;
SettingStore.themeMode = mode; // Persistir
}
}

text
Uso:
dart
class MyWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themeNotifierProvider);
return Text('Tema: $theme');
}
}
text

2. AsyncNotifierProvider (Estado Asíncrono)

Para datos que se cargan de forma asíncrona:

dart
@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);
});
}
}

text
Uso:
dart
final status = ref.watch(serverStatusProvider(server));

status.when(
data: (data) => StatusWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)

text

3. StreamProvider (Datos en Tiempo Real)

Para flujos de datos continuos:

dart
@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;
}

text
Uso:
dart
final cpu = ref.watch(cpuUsageProvider(server));

cpu.when(
data: (usage) => CpuChart(usage),
loading: () => CircularProgressIndicator(),
error: (error, stack) => ErrorWidget(error),
)

text

4. Family Providers (Parametrizados)

Providers que aceptan parámetros:

dart
@riverpod
Future<List<Container>> containers(ContainersRef ref, Server server) async {
final client = await ref.watch(sshClientProvider(server).future);
return await client.listContainers();
}
text
Uso:
dart
final containers = ref.watch(containersProvider(server));

// Diferentes servidores = diferentes estados en caché
final containers2 = ref.watch(containersProvider(server2));

text

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 │
└─────────────────────────────────────────────┘
text

Ciclo de Vida de la Sesión de Terminal

1. Creación de la Sesión

dart
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,
);
}

text

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
text

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

dart
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());
}
}

text

Pellizcar para Ampliar (Pinch-to-Zoom)

dart
GestureDetector(
onScaleStart: () => _baseFontSize = currentFontSize,
onScaleUpdate: (details) {
final newFontSize = _baseFontSize * details.scale;
resize(newFontSize);
},
)
text

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:

sh
brew install --cask server-box
text
Características:
- Integración nativa con la barra de menú
- Soporte para Intel y Apple Silicon

Linux

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 │
└─────────────────────────────────────┘
text

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
text
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 widget

Custom 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

bash

Run in development mode


flutter run

Run on specific device


flutter run -d <device-id>
text

Production Build

The project uses fl_build for building:

bash

Build for specific platform


dart run fl_build -p <platform>

Available platforms:


- ios


- android


- macos


- linux


- windows


text

Platform-Specific Builds

iOS

bash
dart run fl_build -p ios
text
Requires:
- macOS with Xcode
- CocoaPods
- Apple Developer account for signing

Android

bash
dart run fl_build -p android
text
Requires:
- Android SDK
- Java Development Kit
- Keystore for signing

macOS

bash
dart run fl_build -p macos
text

Linux

bash
dart run fl_build -p linux
text

Windows

bash
dart run fl_build -p windows
text
Requires Windows with Visual Studio.

Pre/Post Build

The make.dart script handles:

- Metadata generation
- Version string updates
- Platform-specific configurations

Troubleshooting

Clean Build

bash
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub get
text

Version Mismatch

Ensure all dependencies are compatible:

bash
flutter pub upgrade
text

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

bash

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
text

Generated Files

Freezed (.freezed.dart)

Immutable data models with union types:

dart
@freezed
class ServerState with _$ServerState {
const factory ServerState.connected() = Connected;
const factory ServerState.disconnected() = Disconnected;
const factory ServerState.error(String message) = Error;
}
text

JSON Serialization (.g.dart)

Generated from json_serializable:

dart
@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);
}

text

Riverpod Providers (.g.dart)

Generated from @riverpod annotation:

dart
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() => 0;
}
text

Hive Adapters (.g.dart)

Auto-generated for Hive models (hive_ce):

dart
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}
text

Localization Generation

bash
flutter gen-l10n
text
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:

dart
@riverpod
class Settings extends _$Settings {
@override
SettingsModel build() {
return SettingsModel.defaults();
}

void update(SettingsModel newSettings) {
state = newSettings;
}
}

text

AsyncNotifierProvider

State that loads asynchronously with loading/error states:

dart
@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));
}
}

text

StreamProvider

Real-time data from streams:

dart
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}
text

State Patterns

Loading States

dart
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
text

Family Providers

Parameterized providers:

dart
@riverpod
List<Container> containers(ContainersRef ref, Server server) {
return containerService.list(server);
}
text

Auto-Dispose

Providers that dispose when no longer referenced:

dart
@Riverpod(keepAlive: false)
class TempState extends _$TempState {
// ...
}
text

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

dart
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}
text

Modifying State

dart
ref.read(settingsProvider.notifier).update(newSettings);
text
---

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
text

Core Layer (lib/core/)

Contains utilities, extensions, and routing configuration:

- Extensions: Dart extensions for common types
- Routes: App routing configuration
- Utils: Shared utility functions

Data 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 models

Providers (lib/data/provider/)

Riverpod providers for dependency injection and state management:

- Server providers
- UI state providers
- Service providers

Stores (lib/data/store/)

Hive-based local storage:

- Server storage
- Settings storage
- Cache storage

View 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 pages

Widgets (lib/view/widget/)

Reusable UI components:

- Server cards
- Status charts
- Input components
- Dialogs

Generated Files

- lib/generated/l10n/ - Auto-generated localization
- *.g.dart - Generated code (json_serializable, freezed, hive, riverpod)
- *.freezed.dart - Freezed immutable classes

Packages 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

bash

Run all tests


flutter test

Run specific test file


flutter test test/battery_test.dart

Run with coverage


flutter test --coverage
text

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:

dart
test('should calculate CPU percentage', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});
text

Widget Tests

Test UI components:

dart
testWidgets('ServerCard displays server name', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: ServerCard(server: testServer),
),
),
);

expect(find.text('Test Server'), findsOneWidget);
});

text

Provider Tests

Test Riverpod providers:

dart
test('serverStatusProvider returns status', () async {
final container = ProviderContainer();
final status = await container.read(serverStatusProvider(testServer).future);
expect(status, isA<StatusModel>());
});
text

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 │
└─────────────────────────────────────┘
text

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
text
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 wider

Eigene 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

bash

Im Entwicklungsmodus ausführen


flutter run

Auf einem bestimmten Gerät ausführen


flutter run -d <device-id>
text

Produktions-Build

Das Projekt verwendet fl_build zum Bauen:

bash

Für eine bestimmte Plattform bauen


dart run fl_build -p <platform>

Verfügbare Plattformen:


- ios


- android


- macos


- linux


- windows


text

Plattformspezifische Builds

iOS

bash
dart run fl_build -p ios
text
Erfordert:
- macOS mit Xcode
- CocoaPods
- Apple Developer Account für die Signierung

Android

bash
dart run fl_build -p android
text
Erfordert:
- Android SDK
- Java Development Kit
- Keystore für die Signierung

macOS

bash
dart run fl_build -p macos
text

Linux

bash
dart run fl_build -p linux
text

Windows

bash
dart run fl_build -p windows
text
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

bash
flutter clean
dart run build_runner build --delete-conflicting-outputs
flutter pub get
text

Versions-Konflikt

Stellen Sie sicher, dass alle Abhängigkeiten kompatibel sind:

bash
flutter pub upgrade
text

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

bash

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
text

Generierte Dateien

Freezed (.freezed.dart)

Unveränderliche Datenmodelle mit Union Types:

dart
@freezed
class ServerState with _$ServerState {
const factory ServerState.connected() = Connected;
const factory ServerState.disconnected() = Disconnected;
const factory ServerState.error(String message) = Error;
}
text

JSON-Serialisierung (.g.dart)

Generiert durch json_serializable:

dart
@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);
}

text

Riverpod Provider (.g.dart)

Generiert aus der @riverpod Annotation:

dart
@riverpod
class MyNotifier extends _$MyNotifier {
@override
int build() => 0;
}
text

Hive-Adapter (.g.dart)

Automatisch generiert für Hive-Modelle (hive_ce):

dart
@HiveType(typeId: 0)
class ServerModel {
@HiveField(0)
final String id;
}
text

Generierung der Lokalisierung

bash
flutter gen-l10n
text
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:

dart
@riverpod
class Settings extends _$Settings {
@override
SettingsModel build() {
return SettingsModel.defaults();
}

void update(SettingsModel newSettings) {
state = newSettings;
}
}

text

AsyncNotifierProvider

Zustand, der asynchron mit Lade-/Fehlerzuständen geladen wird:

dart
@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));
}
}

text

StreamProvider

Echtzeitdaten aus Streams:

dart
@riverpod
Stream<CpuUsage> cpuUsage(CpuUsageRef ref, Server server) {
return cpuService.monitor(server);
}
text

Zustandsmuster

Ladezustände

dart
state.when(
data: (data) => DataWidget(data),
loading: () => LoadingWidget(),
error: (error, stack) => ErrorWidget(error),
)
text

Family Provider

Parametrisierte Provider:

dart
@riverpod
List<Container> containers(ContainersRef ref, Server server) {
return containerService.list(server);
}
text

Auto-Dispose

Provider, die verworfen werden, wenn sie nicht mehr referenziert werden:

dart
@Riverpod(keepAlive: false)
class TempState extends _$TempState {
// ...
}
text

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

dart
class ServerWidget extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final status = ref.watch(serverStatusProvider(server));
return status.when(...);
}
}
text

Zustand ändern

dart
ref.read(settingsProvider.notifier).update(newSettings);
text
---

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
text

Kernschicht (lib/core/)

Enthält Dienstprogramme, Erweiterungen und Routing-Konfiguration:

- Erweiterungen: Dart-Erweiterungen für gängige Typen
- Routen: App-Routing-Konfiguration
- Dienstprogramme: Gemeinsame Hilfsfunktionen

Datenschicht (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 Modelle

Provider (lib/data/provider/)

Riverpod Provider für Dependency Injection und Zustandsverwaltung:

- Server Provider
- UI-Zustands-Provider
- Service Provider

Stores (lib/data/store/)

Hive-basierte lokale Speicherung:

- Server-Speicher
- Einstellungs-Speicher
- Cache-Speicher

UI-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-Seiten

Widgets (lib/view/widget/)

Wiederverwendbare UI-Komponenten:

- Server-Karten
- Status-Diagramme
- Eingabe-Komponenten
- Dialoge

Generierte Dateien

- lib/generated/l10n/ - Automatisch generierte Lokalisierung
- *.g.dart - Generierter Code (json_serializable, freezed, hive, riverpod)
- *.freezed.dart - Unveränderliche Freezed-Klassen

Verzeichnis "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

bash

Alle Tests ausführen


flutter test

Bestimmte Testdatei ausführen


flutter test test/battery_test.dart

Mit Coverage ausführen


flutter test --coverage
text

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:

dart
test('sollte CPU-Prozentsatz berechnen', () {
final cpu = CpuModel(usage: 75.0);
expect(cpu.usagePercentage, '75%');
});
text

Widget-Tests

UI-Komponenten testen:

dart
testWidgets('ServerCard zeigt Servernamen an', (tester) async {
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
home: ServerCard(server: testServer),
),
),
);

expect(find.text('Test Server'), findsOneWidget);
});

text

Provider-Tests

Riverpod Provider testen:

dart
test('serverStatusProvider gibt Status zurück', () async {
final container = ProviderContainer();
final status = await container.read(serverStatusProvider(testServer).future);
expect(status, isA<StatusModel>());
});
text

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
:::

json
[
{
"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

json
[
{
"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

json
{
"Anzeigename": "Shell-Befehl"
}

Beispiel:

json
{
"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):

json
{
"server_card_top_right": "Ihr-Befehl-hier"
}

Tipps

Absolute Pfade verwenden:

json
{"Mein Skript": "/usr/local/bin/mein-skript.sh"}

Pipe-Befehle:

json
{"Top-Prozess": "ps aux | sort -rk 3 | head -5"}

Ausgabe formatieren:

json
{"CPU-Last": "uptime | awk -F'load average:' '{print $2}'"}

Befehle schnell halten: Unter 5 Sekunden für das beste Erlebnis.

Ausgabe begrenzen:

json
{"Logs": "tail -20 /var/log/syslog"}

Sicherheit

Befehle werden mit den Berechtigungen des SSH-Benutzers ausgeführt. Vermeiden Sie Befehle, die den Systemzustand ändern.

---