From 6338c6ce6b15b5332376746ade86d262f4c20c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Thu, 29 Jan 2026 20:27:21 +0800 Subject: [PATCH] opt.: docs l10n (#1036) * opt.: docs l10n Fixes #1035 * opt. * rm: redundant docs * rm: features chapter * opt.: docs l10n Fixes #1035 * fix --- docs/astro.config.mjs | 132 +++-- .../content/docs/advanced/json-settings.md | 14 +- .../content/docs/configuration/appearance.md | 124 ----- docs/src/content/docs/configuration/backup.md | 80 --- .../content/docs/configuration/jump-server.md | 88 ---- .../docs/configuration/localizations.md | 93 ---- docs/src/content/docs/configuration/server.md | 90 ---- docs/src/content/docs/configuration/sftp.md | 118 ----- .../content/docs/configuration/terminal.md | 127 ----- .../content/docs/de/advanced/bulk-import.md | 83 +++ .../docs/de/advanced/custom-commands.md | 72 +++ .../content/docs/de/advanced/custom-logo.md | 54 ++ .../content/docs/de/advanced/json-settings.md | 64 +++ .../docs/de/advanced/troubleshooting.md | 118 +++++ docs/src/content/docs/de/advanced/widgets.md | 90 ++++ .../docs/de/development/architecture.md | 86 +++ .../content/docs/de/development/building.md | 116 +++++ .../content/docs/de/development/codegen.md | 98 ++++ docs/src/content/docs/de/development/state.md | 115 ++++ .../content/docs/de/development/structure.md | 96 ++++ .../content/docs/de/development/testing.md | 113 ++++ docs/src/content/docs/de/index.mdx | 46 ++ docs/src/content/docs/de/installation.mdx | 51 ++ docs/src/content/docs/de/introduction.mdx | 32 ++ docs/src/content/docs/de/platforms/desktop.md | 80 +++ docs/src/content/docs/de/platforms/mobile.md | 77 +++ .../docs/de/principles/architecture.md | 214 ++++++++ docs/src/content/docs/de/principles/sftp.md | 490 ++++++++++++++++++ docs/src/content/docs/de/principles/ssh.md | 305 +++++++++++ docs/src/content/docs/de/principles/state.md | 221 ++++++++ .../content/docs/de/principles/terminal.md | 198 +++++++ docs/src/content/docs/de/quick-start.mdx | 45 ++ .../content/docs/development/architecture.md | 2 +- docs/src/content/docs/development/building.md | 2 +- docs/src/content/docs/development/codegen.md | 2 +- docs/src/content/docs/development/state.md | 2 +- .../src/content/docs/development/structure.md | 4 +- .../content/docs/es/advanced/bulk-import.md | 83 +++ .../docs/es/advanced/custom-commands.md | 72 +++ .../content/docs/es/advanced/custom-logo.md | 54 ++ .../content/docs/es/advanced/json-settings.md | 64 +++ .../docs/es/advanced/troubleshooting.md | 118 +++++ docs/src/content/docs/es/advanced/widgets.md | 90 ++++ .../docs/es/development/architecture.md | 86 +++ .../content/docs/es/development/building.md | 116 +++++ .../content/docs/es/development/codegen.md | 98 ++++ docs/src/content/docs/es/development/state.md | 115 ++++ .../content/docs/es/development/structure.md | 96 ++++ .../content/docs/es/development/testing.md | 113 ++++ docs/src/content/docs/es/index.mdx | 46 ++ docs/src/content/docs/es/installation.mdx | 51 ++ docs/src/content/docs/es/introduction.mdx | 32 ++ docs/src/content/docs/es/platforms/desktop.md | 80 +++ docs/src/content/docs/es/platforms/mobile.md | 77 +++ .../docs/es/principles/architecture.md | 214 ++++++++ docs/src/content/docs/es/principles/sftp.md | 490 ++++++++++++++++++ docs/src/content/docs/es/principles/ssh.md | 305 +++++++++++ docs/src/content/docs/es/principles/state.md | 167 ++++++ .../content/docs/es/principles/terminal.md | 198 +++++++ docs/src/content/docs/es/quick-start.mdx | 45 ++ docs/src/content/docs/features/docker.md | 55 -- docs/src/content/docs/features/monitoring.md | 73 --- docs/src/content/docs/features/network.md | 67 --- docs/src/content/docs/features/process.md | 56 -- docs/src/content/docs/features/pve.md | 105 ---- docs/src/content/docs/features/snippets.md | 60 --- .../content/docs/fr/advanced/bulk-import.md | 83 +++ .../docs/fr/advanced/custom-commands.md | 72 +++ .../content/docs/fr/advanced/custom-logo.md | 54 ++ .../content/docs/fr/advanced/json-settings.md | 64 +++ .../docs/fr/advanced/troubleshooting.md | 118 +++++ docs/src/content/docs/fr/advanced/widgets.md | 90 ++++ .../docs/fr/development/architecture.md | 86 +++ .../content/docs/fr/development/building.md | 116 +++++ .../content/docs/fr/development/codegen.md | 98 ++++ docs/src/content/docs/fr/development/state.md | 115 ++++ .../content/docs/fr/development/structure.md | 96 ++++ .../content/docs/fr/development/testing.md | 113 ++++ docs/src/content/docs/fr/index.mdx | 46 ++ docs/src/content/docs/fr/installation.mdx | 51 ++ docs/src/content/docs/fr/introduction.mdx | 32 ++ docs/src/content/docs/fr/platforms/desktop.md | 80 +++ docs/src/content/docs/fr/platforms/mobile.md | 77 +++ .../docs/fr/principles/architecture.md | 214 ++++++++ docs/src/content/docs/fr/principles/sftp.md | 490 ++++++++++++++++++ docs/src/content/docs/fr/principles/ssh.md | 305 +++++++++++ docs/src/content/docs/fr/principles/state.md | 167 ++++++ .../content/docs/fr/principles/terminal.md | 198 +++++++ docs/src/content/docs/fr/quick-start.mdx | 45 ++ docs/src/content/docs/index.mdx | 6 +- docs/src/content/docs/installation.mdx | 11 +- docs/src/content/docs/introduction.mdx | 11 +- .../content/docs/ja/advanced/bulk-import.md | 83 +++ .../docs/ja/advanced/custom-commands.md | 72 +++ .../content/docs/ja/advanced/custom-logo.md | 54 ++ .../content/docs/ja/advanced/json-settings.md | 64 +++ .../docs/ja/advanced/troubleshooting.md | 118 +++++ docs/src/content/docs/ja/advanced/widgets.md | 90 ++++ .../docs/ja/development/architecture.md | 86 +++ .../content/docs/ja/development/building.md | 116 +++++ .../content/docs/ja/development/codegen.md | 98 ++++ docs/src/content/docs/ja/development/state.md | 115 ++++ .../content/docs/ja/development/structure.md | 96 ++++ .../content/docs/ja/development/testing.md | 113 ++++ docs/src/content/docs/ja/index.mdx | 46 ++ docs/src/content/docs/ja/installation.mdx | 51 ++ docs/src/content/docs/ja/introduction.mdx | 32 ++ docs/src/content/docs/ja/platforms/desktop.md | 78 +++ docs/src/content/docs/ja/platforms/mobile.md | 77 +++ .../docs/ja/principles/architecture.md | 214 ++++++++ docs/src/content/docs/ja/principles/sftp.md | 490 ++++++++++++++++++ docs/src/content/docs/ja/principles/ssh.md | 305 +++++++++++ docs/src/content/docs/ja/principles/state.md | 167 ++++++ .../content/docs/ja/principles/terminal.md | 198 +++++++ docs/src/content/docs/ja/quick-start.mdx | 45 ++ docs/src/content/docs/platforms/desktop.md | 2 +- docs/src/content/docs/platforms/mobile.md | 6 +- docs/src/content/docs/platforms/watchos.md | 55 -- .../content/docs/principles/architecture.md | 2 +- docs/src/content/docs/principles/ssh.md | 2 +- docs/src/content/docs/principles/state.md | 2 +- docs/src/content/docs/quick-start.mdx | 10 +- .../content/docs/zh/advanced/bulk-import.md | 83 +++ .../docs/zh/advanced/custom-commands.md | 72 +++ .../content/docs/zh/advanced/custom-logo.md | 54 ++ .../content/docs/zh/advanced/json-settings.md | 64 +++ .../docs/zh/advanced/troubleshooting.md | 118 +++++ docs/src/content/docs/zh/advanced/widgets.md | 90 ++++ .../docs/zh/development/architecture.md | 86 +++ .../content/docs/zh/development/building.md | 116 +++++ .../content/docs/zh/development/codegen.md | 98 ++++ docs/src/content/docs/zh/development/state.md | 115 ++++ .../content/docs/zh/development/structure.md | 96 ++++ .../content/docs/zh/development/testing.md | 113 ++++ docs/src/content/docs/zh/index.mdx | 46 ++ docs/src/content/docs/zh/installation.mdx | 53 ++ docs/src/content/docs/zh/introduction.mdx | 32 ++ docs/src/content/docs/zh/platforms/desktop.md | 74 +++ docs/src/content/docs/zh/platforms/mobile.md | 77 +++ .../docs/zh/principles/architecture.md | 214 ++++++++ docs/src/content/docs/zh/principles/sftp.md | 490 ++++++++++++++++++ docs/src/content/docs/zh/principles/ssh.md | 305 +++++++++++ docs/src/content/docs/zh/principles/state.md | 301 +++++++++++ .../content/docs/zh/principles/terminal.md | 248 +++++++++ docs/src/content/docs/zh/quick-start.mdx | 45 ++ docs/src/styles/custom.css | 2 +- 146 files changed, 14398 insertions(+), 1287 deletions(-) delete mode 100644 docs/src/content/docs/configuration/appearance.md delete mode 100644 docs/src/content/docs/configuration/backup.md delete mode 100644 docs/src/content/docs/configuration/jump-server.md delete mode 100644 docs/src/content/docs/configuration/localizations.md delete mode 100644 docs/src/content/docs/configuration/server.md delete mode 100644 docs/src/content/docs/configuration/sftp.md delete mode 100644 docs/src/content/docs/configuration/terminal.md create mode 100644 docs/src/content/docs/de/advanced/bulk-import.md create mode 100644 docs/src/content/docs/de/advanced/custom-commands.md create mode 100644 docs/src/content/docs/de/advanced/custom-logo.md create mode 100644 docs/src/content/docs/de/advanced/json-settings.md create mode 100644 docs/src/content/docs/de/advanced/troubleshooting.md create mode 100644 docs/src/content/docs/de/advanced/widgets.md create mode 100644 docs/src/content/docs/de/development/architecture.md create mode 100644 docs/src/content/docs/de/development/building.md create mode 100644 docs/src/content/docs/de/development/codegen.md create mode 100644 docs/src/content/docs/de/development/state.md create mode 100644 docs/src/content/docs/de/development/structure.md create mode 100644 docs/src/content/docs/de/development/testing.md create mode 100644 docs/src/content/docs/de/index.mdx create mode 100644 docs/src/content/docs/de/installation.mdx create mode 100644 docs/src/content/docs/de/introduction.mdx create mode 100644 docs/src/content/docs/de/platforms/desktop.md create mode 100644 docs/src/content/docs/de/platforms/mobile.md create mode 100644 docs/src/content/docs/de/principles/architecture.md create mode 100644 docs/src/content/docs/de/principles/sftp.md create mode 100644 docs/src/content/docs/de/principles/ssh.md create mode 100644 docs/src/content/docs/de/principles/state.md create mode 100644 docs/src/content/docs/de/principles/terminal.md create mode 100644 docs/src/content/docs/de/quick-start.mdx create mode 100644 docs/src/content/docs/es/advanced/bulk-import.md create mode 100644 docs/src/content/docs/es/advanced/custom-commands.md create mode 100644 docs/src/content/docs/es/advanced/custom-logo.md create mode 100644 docs/src/content/docs/es/advanced/json-settings.md create mode 100644 docs/src/content/docs/es/advanced/troubleshooting.md create mode 100644 docs/src/content/docs/es/advanced/widgets.md create mode 100644 docs/src/content/docs/es/development/architecture.md create mode 100644 docs/src/content/docs/es/development/building.md create mode 100644 docs/src/content/docs/es/development/codegen.md create mode 100644 docs/src/content/docs/es/development/state.md create mode 100644 docs/src/content/docs/es/development/structure.md create mode 100644 docs/src/content/docs/es/development/testing.md create mode 100644 docs/src/content/docs/es/index.mdx create mode 100644 docs/src/content/docs/es/installation.mdx create mode 100644 docs/src/content/docs/es/introduction.mdx create mode 100644 docs/src/content/docs/es/platforms/desktop.md create mode 100644 docs/src/content/docs/es/platforms/mobile.md create mode 100644 docs/src/content/docs/es/principles/architecture.md create mode 100644 docs/src/content/docs/es/principles/sftp.md create mode 100644 docs/src/content/docs/es/principles/ssh.md create mode 100644 docs/src/content/docs/es/principles/state.md create mode 100644 docs/src/content/docs/es/principles/terminal.md create mode 100644 docs/src/content/docs/es/quick-start.mdx delete mode 100644 docs/src/content/docs/features/docker.md delete mode 100644 docs/src/content/docs/features/monitoring.md delete mode 100644 docs/src/content/docs/features/network.md delete mode 100644 docs/src/content/docs/features/process.md delete mode 100644 docs/src/content/docs/features/pve.md delete mode 100644 docs/src/content/docs/features/snippets.md create mode 100644 docs/src/content/docs/fr/advanced/bulk-import.md create mode 100644 docs/src/content/docs/fr/advanced/custom-commands.md create mode 100644 docs/src/content/docs/fr/advanced/custom-logo.md create mode 100644 docs/src/content/docs/fr/advanced/json-settings.md create mode 100644 docs/src/content/docs/fr/advanced/troubleshooting.md create mode 100644 docs/src/content/docs/fr/advanced/widgets.md create mode 100644 docs/src/content/docs/fr/development/architecture.md create mode 100644 docs/src/content/docs/fr/development/building.md create mode 100644 docs/src/content/docs/fr/development/codegen.md create mode 100644 docs/src/content/docs/fr/development/state.md create mode 100644 docs/src/content/docs/fr/development/structure.md create mode 100644 docs/src/content/docs/fr/development/testing.md create mode 100644 docs/src/content/docs/fr/index.mdx create mode 100644 docs/src/content/docs/fr/installation.mdx create mode 100644 docs/src/content/docs/fr/introduction.mdx create mode 100644 docs/src/content/docs/fr/platforms/desktop.md create mode 100644 docs/src/content/docs/fr/platforms/mobile.md create mode 100644 docs/src/content/docs/fr/principles/architecture.md create mode 100644 docs/src/content/docs/fr/principles/sftp.md create mode 100644 docs/src/content/docs/fr/principles/ssh.md create mode 100644 docs/src/content/docs/fr/principles/state.md create mode 100644 docs/src/content/docs/fr/principles/terminal.md create mode 100644 docs/src/content/docs/fr/quick-start.mdx create mode 100644 docs/src/content/docs/ja/advanced/bulk-import.md create mode 100644 docs/src/content/docs/ja/advanced/custom-commands.md create mode 100644 docs/src/content/docs/ja/advanced/custom-logo.md create mode 100644 docs/src/content/docs/ja/advanced/json-settings.md create mode 100644 docs/src/content/docs/ja/advanced/troubleshooting.md create mode 100644 docs/src/content/docs/ja/advanced/widgets.md create mode 100644 docs/src/content/docs/ja/development/architecture.md create mode 100644 docs/src/content/docs/ja/development/building.md create mode 100644 docs/src/content/docs/ja/development/codegen.md create mode 100644 docs/src/content/docs/ja/development/state.md create mode 100644 docs/src/content/docs/ja/development/structure.md create mode 100644 docs/src/content/docs/ja/development/testing.md create mode 100644 docs/src/content/docs/ja/index.mdx create mode 100644 docs/src/content/docs/ja/installation.mdx create mode 100644 docs/src/content/docs/ja/introduction.mdx create mode 100644 docs/src/content/docs/ja/platforms/desktop.md create mode 100644 docs/src/content/docs/ja/platforms/mobile.md create mode 100644 docs/src/content/docs/ja/principles/architecture.md create mode 100644 docs/src/content/docs/ja/principles/sftp.md create mode 100644 docs/src/content/docs/ja/principles/ssh.md create mode 100644 docs/src/content/docs/ja/principles/state.md create mode 100644 docs/src/content/docs/ja/principles/terminal.md create mode 100644 docs/src/content/docs/ja/quick-start.mdx delete mode 100644 docs/src/content/docs/platforms/watchos.md create mode 100644 docs/src/content/docs/zh/advanced/bulk-import.md create mode 100644 docs/src/content/docs/zh/advanced/custom-commands.md create mode 100644 docs/src/content/docs/zh/advanced/custom-logo.md create mode 100644 docs/src/content/docs/zh/advanced/json-settings.md create mode 100644 docs/src/content/docs/zh/advanced/troubleshooting.md create mode 100644 docs/src/content/docs/zh/advanced/widgets.md create mode 100644 docs/src/content/docs/zh/development/architecture.md create mode 100644 docs/src/content/docs/zh/development/building.md create mode 100644 docs/src/content/docs/zh/development/codegen.md create mode 100644 docs/src/content/docs/zh/development/state.md create mode 100644 docs/src/content/docs/zh/development/structure.md create mode 100644 docs/src/content/docs/zh/development/testing.md create mode 100644 docs/src/content/docs/zh/index.mdx create mode 100644 docs/src/content/docs/zh/installation.mdx create mode 100644 docs/src/content/docs/zh/introduction.mdx create mode 100644 docs/src/content/docs/zh/platforms/desktop.md create mode 100644 docs/src/content/docs/zh/platforms/mobile.md create mode 100644 docs/src/content/docs/zh/principles/architecture.md create mode 100644 docs/src/content/docs/zh/principles/sftp.md create mode 100644 docs/src/content/docs/zh/principles/ssh.md create mode 100644 docs/src/content/docs/zh/principles/state.md create mode 100644 docs/src/content/docs/zh/principles/terminal.md create mode 100644 docs/src/content/docs/zh/quick-start.mdx diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index db6e856b..6b09adae 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -6,8 +6,35 @@ import starlight from '@astrojs/starlight'; export default defineConfig({ integrations: [ starlight({ - title: 'Flutter Server Box', + title: 'Server Box', description: 'A comprehensive cross-platform server management application built with Flutter', + defaultLocale: 'root', + locales: { + root: { + label: 'English', + lang: 'en', + }, + zh: { + label: '简体中文', + lang: 'zh', + }, + de: { + label: 'Deutsch', + lang: 'de', + }, + fr: { + label: 'Français', + lang: 'fr', + }, + es: { + label: 'Español', + lang: 'es', + }, + ja: { + label: '日本語', + lang: 'ja', + }, + }, logo: { src: './src/assets/logo.svg', }, @@ -17,73 +44,84 @@ export default defineConfig({ sidebar: [ { label: 'Getting Started', + translations: { + zh: '开始使用', + de: 'Erste Schritte', + fr: 'Mise en route', + es: 'Primeros pasos', + ja: 'はじめに', + }, items: [ - { label: 'Introduction', slug: 'introduction' }, - { label: 'Installation', slug: 'installation' }, - { label: 'Quick Start', slug: 'quick-start' }, - ], - }, - { - label: 'Features', - items: [ - { label: 'Server Monitoring', slug: 'features/monitoring' }, - { label: 'Docker Management', slug: 'features/docker' }, - { label: 'Process & Services', slug: 'features/process' }, - { label: 'Command Snippets', slug: 'features/snippets' }, - { label: 'Network Tools', slug: 'features/network' }, - { label: 'PVE (Proxmox)', slug: 'features/pve' }, - ], - }, - { - label: 'Configuration', - items: [ - { label: 'Server Setup', slug: 'configuration/server' }, - { label: 'Terminal & SSH', slug: 'configuration/terminal' }, - { label: 'SFTP File Browser', slug: 'configuration/sftp' }, - { label: 'Jump Server', slug: 'configuration/jump-server' }, - { label: 'Backup & Restore', slug: 'configuration/backup' }, - { label: 'Appearance', slug: 'configuration/appearance' }, - { label: 'Localizations', slug: 'configuration/localizations' }, + { label: 'Introduction', translations: { zh: '介绍', de: 'Einführung', fr: 'Introduction', es: 'Introducción', ja: 'はじめに' }, slug: 'introduction' }, + { label: 'Installation', translations: { zh: '安装', de: 'Installation', fr: 'Installation', es: 'Instalación', ja: 'インストール' }, slug: 'installation' }, + { label: 'Quick Start', translations: { zh: '快速开始', de: 'Schnellstart', fr: 'Démarrage rapide', es: 'Inicio rápido', ja: 'クイックスタート' }, slug: 'quick-start' }, ], }, { label: 'Platform Features', + translations: { + zh: '平台特性', + de: 'Plattformfunktionen', + fr: 'Fonctionnalités de la plateforme', + es: 'Características de la plataforma', + ja: 'プラットフォーム機能', + }, items: [ - { label: 'Mobile', slug: 'platforms/mobile' }, - { label: 'Desktop', slug: 'platforms/desktop' }, - { label: 'watchOS', slug: 'platforms/watchos' }, + { label: 'Mobile', translations: { zh: '移动端', de: 'Mobil', fr: 'Mobile', es: 'Móvil', ja: 'モバイル' }, slug: 'platforms/mobile' }, + { label: 'Desktop', translations: { zh: '桌面端', de: 'Desktop', fr: 'Bureau', es: 'Escritorio', ja: 'デスクトップ' }, slug: 'platforms/desktop' }, ], }, { label: 'Advanced', + translations: { + zh: '进阶', + de: 'Fortgeschritten', + fr: 'Avancé', + es: 'Avanzado', + ja: '高度な設定', + }, items: [ - { label: 'Bulk Import Servers', slug: 'advanced/bulk-import' }, - { label: 'Widget Setup', slug: 'advanced/widgets' }, - { label: 'Custom Commands', slug: 'advanced/custom-commands' }, - { label: 'Custom Logo', slug: 'advanced/custom-logo' }, - { label: 'JSON Settings', slug: 'advanced/json-settings' }, - { label: 'Common Issues', slug: 'advanced/troubleshooting' }, + { label: 'Bulk Import Servers', translations: { zh: '批量导入服务器', de: 'Server-Massenimport', fr: 'Importation massive de serveurs', es: 'Importación masiva de servidores', ja: 'サーバーの一括インポート' }, slug: 'advanced/bulk-import' }, + { label: 'Widget Setup', translations: { zh: '小组件设置', de: 'Widget-Einrichtung', fr: 'Configuration du widget', es: 'Configuración de widgets', ja: 'ウィジェット設定' }, slug: 'advanced/widgets' }, + { label: 'Custom Commands', translations: { zh: '自定义命令', de: 'Benutzerdefinierte Befehle', fr: 'Commandes personnalisées', es: 'Comandos personalizados', ja: 'カスタムコマンド' }, slug: 'advanced/custom-commands' }, + { label: 'Custom Logo', translations: { zh: '自定义 Logo', de: 'Benutzerdefiniertes Logo', fr: 'Logo personnalisé', es: 'Logo personalizado', ja: 'カスタムロゴ' }, slug: 'advanced/custom-logo' }, + { label: 'JSON Settings', translations: { zh: 'JSON 设置', de: 'JSON-Einstellungen', fr: 'Paramètres JSON', es: 'Ajustes JSON', ja: 'JSON 設定' }, slug: 'advanced/json-settings' }, + { label: 'Common Issues', translations: { zh: '常见问题', de: 'Häufige Probleme', fr: 'Problèmes courants', es: 'Problemas comunes', ja: 'よくある質問' }, slug: 'advanced/troubleshooting' }, ], }, { label: 'How It Works', + translations: { + zh: '工作原理', + de: 'Wie es funktioniert', + fr: 'Comment ça marche', + es: 'Cómo funciona', + ja: '仕組み', + }, items: [ - { label: 'Architecture', slug: 'principles/architecture' }, - { label: 'SSH Connection', slug: 'principles/ssh' }, - { label: 'Terminal', slug: 'principles/terminal' }, - { label: 'SFTP', slug: 'principles/sftp' }, - { label: 'State Management', slug: 'principles/state' }, + { label: 'Architecture', translations: { zh: '架构', de: 'Architektur', fr: 'Architecture', es: 'Arquitectura', ja: 'アーキテクチャ' }, slug: 'principles/architecture' }, + { label: 'SSH Connection', translations: { zh: 'SSH 连接', de: 'SSH-Verbindung', fr: 'Connexion SSH', es: 'Conexión SSH', ja: 'SSH 接続' }, slug: 'principles/ssh' }, + { label: 'Terminal', translations: { zh: '终端', de: 'Terminal', fr: 'Terminal', es: 'Terminal', ja: 'ターミナル' }, slug: 'principles/terminal' }, + { label: 'SFTP', translations: { zh: 'SFTP', de: 'SFTP', fr: 'SFTP', es: 'SFTP', ja: 'SFTP' }, slug: 'principles/sftp' }, + { label: 'State Management', translations: { zh: '状态管理', de: 'Zustandsverwaltung', fr: 'Gestion d\'état', es: 'Gestión de estado', ja: '状態管理' }, slug: 'principles/state' }, ], }, { label: 'Development', + translations: { + zh: '开发', + de: 'Entwicklung', + fr: 'Développement', + es: 'Desarrollo', + ja: '開発', + }, items: [ - { label: 'Project Structure', slug: 'development/structure' }, - { label: 'Architecture', slug: 'development/architecture' }, - { label: 'State Management', slug: 'development/state' }, - { label: 'Code Generation', slug: 'development/codegen' }, - { label: 'Building', slug: 'development/building' }, - { label: 'Testing', slug: 'development/testing' }, + { label: 'Project Structure', translations: { zh: '项目结构', de: 'Projektstruktur', fr: 'Structure du projet', es: 'Estructura del proyecto', ja: 'プロジェクト構造' }, slug: 'development/structure' }, + { label: 'Architecture', translations: { zh: '架构', de: 'Architektur', fr: 'Architecture', es: 'Arquitectura', ja: 'アーキテクチャ' }, slug: 'development/architecture' }, + { label: 'State Management', translations: { zh: '状态管理', de: 'Zustandsverwaltung', fr: 'Gestion d\'état', es: 'Gestión de estado', ja: '状態管理' }, slug: 'development/state' }, + { label: 'Code Generation', translations: { zh: '代码生成', de: 'Code-Generierung', fr: 'Génération de code', es: 'Generación de código', ja: 'コード生成' }, slug: 'development/codegen' }, + { label: 'Building', translations: { zh: '构建', de: 'Bauen', fr: 'Construction', es: 'Construcción', ja: 'ビルド' }, slug: 'development/building' }, + { label: 'Testing', translations: { zh: '测试', de: 'Testen', fr: 'Tests', es: 'Pruebas', ja: 'テスト' }, slug: 'development/testing' }, ], }, ], diff --git a/docs/src/content/docs/advanced/json-settings.md b/docs/src/content/docs/advanced/json-settings.md index 18fb531e..63b67f4c 100644 --- a/docs/src/content/docs/advanced/json-settings.md +++ b/docs/src/content/docs/advanced/json-settings.md @@ -11,22 +11,12 @@ Long-press **Settings** in drawer to open JSON editor. ## Common Hidden Settings -### serverTabUseOldUI - -Use old server tab UI. - -```json -{"serverTabUseOldUI": true} -``` - -**Type:** boolean | **Default:** false - -### timeout +### timeOut Connection timeout in seconds. ```json -{"timeout": 10} +{"timeOut": 10} ``` **Type:** integer | **Default:** 5 | **Range:** 1-60 diff --git a/docs/src/content/docs/configuration/appearance.md b/docs/src/content/docs/configuration/appearance.md deleted file mode 100644 index 847e5748..00000000 --- a/docs/src/content/docs/configuration/appearance.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: Appearance -description: Customize the look and feel ---- - -Flutter Server Box offers extensive appearance customization. - -## Themes - -### Light Theme - -Clean, bright interface for daytime use. - -### Dark Theme - -Easy on the eyes for low-light environments. - -### AMOLED Dark - -Pure black background for OLED screens, saves battery. - -### System Theme - -Automatically switches between light and dark based on system settings. - -Set in: **Settings > Appearance > Theme** - -## Terminal Appearance - -### Customization Options - -- **Font Size**: Adjust text size in terminal -- **Font Family**: Choose from available fonts -- **Text Color**: Customize text color -- **Background Color**: Set terminal background -- **Background Opacity**: Adjust transparency -- **Blur Effect**: Enable background blur - -### Terminal Themes - -Create terminal color themes: - -1. Go to Settings > Terminal > Themes -2. Create new theme or edit existing -3. Customize colors: - - Text color - - Background color - - Cursor color - - Selection color - -## Server Cards - -### Card Style - -- **Compact**: Minimal information per card -- **Detailed**: Extended information per card -- **Custom**: Choose which metrics to show - -### Card Order - -1. Go to Settings > Server Card Order -2. Drag cards to reorder -3. Changes apply immediately - -### Card Metrics - -Enable/disable metrics: -- CPU -- Memory -- Disk -- Network -- GPU -- Temperature - -## Charts and Graphs - -### Chart Style - -- **Line**: Continuous line chart -- **Area**: Filled area chart -- **Bar**: Bar chart visualization - -### Chart Colors - -Customize chart colors in: -**Settings > Charts > Colors** - -### Refresh Rate - -Adjust how often charts update: -**Settings > Charts > Refresh Rate** - -- **Power Saving**: 5 seconds -- **Normal**: 2 seconds -- **High Performance**: 1 second - -## Layout - -### Navigation Style - -- **Bottom Navigation**: Mobile-style bottom tabs -- **Side Navigation**: Desktop-style sidebar -- **Tabs**: Classic tab interface - -### View Mode - -- **List**: Vertical list view -- **Grid**: Grid layout for servers -- **Cards**: Card-based layout - -## Icons and Symbols - -Choose icon style: -- **Filled**: Solid icons -- **Outlined**: Line icons -- **Rounded**: Soft, rounded icons - -## Animations - -Control animation speed: -- **Off**: No animations -- **Reduced**: Minimal animations -- **Normal**: Standard animations -- **Enhanced**: Extra animations diff --git a/docs/src/content/docs/configuration/backup.md b/docs/src/content/docs/configuration/backup.md deleted file mode 100644 index 825a9d15..00000000 --- a/docs/src/content/docs/configuration/backup.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: Backup & Restore -description: Backup and restore your app data ---- - -Protect your server configurations and settings with built-in backup functionality. - -## What Gets Backed Up - -- **Server Configurations**: All saved servers -- **SSH Keys**: Imported private keys (encrypted) -- **Snippets**: Saved command snippets -- **Settings**: App preferences - -**Not included:** Passwords (for security) - -## Creating Backups - -### Manual Backup - -1. Settings → Backup -2. Tap **Create Backup** -3. Choose location -4. Backup saved with timestamp - -### Auto Backup - -Settings → Backup → Auto Backup: -- Daily / Weekly / Monthly / Off - -### Cloud Sync - -- **iOS/macOS**: iCloud automatic backup -- **Android**: Google Drive integration - -## Restoring - -### From Local Backup - -1. Settings → Backup → Restore Backup -2. Select backup file -3. Authenticate (biometric/password) -4. Confirm restore - -### From Cloud - -1. Sign in to same cloud account -2. Settings → Backup → Restore from Cloud -3. Select backup from list -4. Authenticate and confirm - -## Important Notes - -### Passwords Not Backed Up - -After restore, you'll need to re-enter passwords for each server. - -**Tip:** Use SSH keys instead - they ARE backed up. - -### Cross-Platform - -Backups work across all platforms (iOS ↔ Android ↔ Desktop). - -## Best Practices - -1. **Enable auto backup** for peace of mind -2. **Test restore** periodically to verify backups work -3. **Backup before** updating app or switching devices -4. **Use SSH keys** to avoid re-entering passwords - -## Troubleshooting - -**Restore failed:** -- Check backup file integrity -- Ensure sufficient storage -- Verify app version compatibility - -**Missing data after restore:** -- Passwords are not backed up (re-enter them) -- Check selective restore settings diff --git a/docs/src/content/docs/configuration/jump-server.md b/docs/src/content/docs/configuration/jump-server.md deleted file mode 100644 index 655f0a75..00000000 --- a/docs/src/content/docs/configuration/jump-server.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Jump Server -description: Route connections through intermediate servers ---- - -Connect to servers behind firewalls or in private networks by routing through an intermediate jump server. - -## What is Jump Server? - -A jump server acts as a gateway to access other servers that: -- Are behind firewalls -- Don't have direct SSH access -- Are in private networks -- Require multi-hop connections - -## Setup - -### Step 1: Configure Jump Server - -Add the jump server as a normal server first: -1. Add server with SSH credentials -2. Test connection to ensure it works -3. This server will be your jump host - -### Step 2: Configure Target Servers - -For each server you want to access via jump: -1. Add target server (credentials for target, not jump) -2. Server settings → Jump Server -3. Select your jump server from list -4. Save - -### Step 3: Connect - -Connect to target server normally. The app automatically: -1. Connects to jump server -2. Tunnels through to target server -3. Maintains connection - -## Use Cases - -### Private Network Access - -``` -Your Device → Jump Server (public IP) → Private Server (10.0.0.x) -``` - -### Behind Firewall - -``` -Your Device → Bastion Host → Internal Server -``` - -### Multi-Hop - -You can chain multiple jump servers for complex networks. - -## Requirements - -- Jump server must be accessible from your device -- Jump server must be able to reach target servers -- SSH keys recommended for jump server (faster authentication) - -## Tips - -- **Use SSH keys** on jump server for faster connections -- **Test direct access** to jump server first -- **Check firewall rules** on both ends -- **Monitor connection** - issues could be on jump or target - -## Troubleshooting - -### Connection Times Out - -- Verify jump server is accessible -- Check jump server can reach target -- Test manually: `ssh -J jump@jump-server user@target-server` - -### Authentication Fails - -- Verify credentials for target server (not jump) -- Check SSH keys if using key authentication - -### Slow Connection - -- Normal for jump connections (extra hop) -- Consider using SSH keys for faster auth -- Check network latency to jump server diff --git a/docs/src/content/docs/configuration/localizations.md b/docs/src/content/docs/configuration/localizations.md deleted file mode 100644 index 80d6f8bb..00000000 --- a/docs/src/content/docs/configuration/localizations.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Localizations -description: Language and region settings ---- - -Flutter Server Box supports 12+ languages with full localization. - -## Supported Languages - -| Language | Status | -|----------|--------| -| English (en) | ✅ Native | -| 简体中文 (zh) | ✅ Native | -| 繁體中文 (zh-Hant) | ✅ Native | -| Deutsch (de) | ✅ Native | -| Français (fr) | ✅ Native | -| Español (es) | ✅ Native | -| Português (pt) | ✅ Native | -| Русский (ru) | ✅ Native | -| Türkçe (tr) | ✅ Native | -| Українська (uk) | ✅ Native | -| Bahasa Indonesia (id) | ✅ Native | -| Nederlands (nl) | ✅ Native | -| 日本語 (ja) | ✅ AI-translated | - -## Changing Language - -1. Go to **Settings > Language** -2. Select preferred language -3. App restarts to apply changes - -## Number Formatting - -Numbers are formatted according to locale: - -- **Thousands separator**: Comma vs period -- **Decimal separator**: Period vs comma -- **Date format**: Locale-specific - -## Time Format - -Choose between: - -- **24-hour**: 13:00, 14:30 -- **12-hour**: 1:00 PM, 2:30 PM - -Set in: **Settings > Time Format** - -## Contributing Translations - -We welcome community translations! - -### Translation Files - -Located in `lib/l10n/`: - -- `app_en.arb` - English (reference) -- `app_zh.arb` - Simplified Chinese -- etc. - -### How to Contribute - -1. Fork the repository -2. Copy `app_en.arb` to `app_YOUR_LOCALE.arb` -3. Translate values (keep keys the same) -4. Test your translations -5. Submit pull request - -### Translation Guidelines - -- Keep technical terms consistent -- Use formal address for professional tone -- Maintain placeholder format: `{variable}` -- Test UI with translated strings - -## Adding New Language - -1. Create new ARB file: `app_xx.arb` -2. Copy all keys from `app_en.arb` -3. Translate all values -4. Add to `l10n.yaml` configuration -5. Run `flutter gen-l10n` -6. Test with new locale - -## RTL Languages - -Right-to-left languages (Arabic, Hebrew) are partially supported. Full RTL layout support is planned for future releases. - -## Quality Notes - -- Some languages are AI-translated and may contain errors -- Native speaker reviews are appreciated -- Report translation issues via GitHub diff --git a/docs/src/content/docs/configuration/server.md b/docs/src/content/docs/configuration/server.md deleted file mode 100644 index c3755295..00000000 --- a/docs/src/content/docs/configuration/server.md +++ /dev/null @@ -1,90 +0,0 @@ ---- -title: Server Setup -description: Configure and manage server connections ---- - -## Adding a Server - -1. Tap the **+** button on the main screen -2. Fill in connection details: - - **Name**: Friendly name for identification - - **Host**: IP address or domain name - - **Port**: SSH port (default: 22) - - **Username**: SSH login user - - **Authentication**: Password or SSH key - -3. Configure optional settings: - - **Initial Directory**: Starting directory for terminal/SFTP - - **Environment**: Custom environment variables - - **Keep-alive Interval**: Connection keep-alive setting - -4. Tap **Save** - -## Connection Types - -### Password Authentication - -Simple username/password authentication: - -- Enter password in the password field -- Password is stored securely (encrypted) -- Requires re-entry on app restart (unless saved) - -### SSH Key Authentication - -More secure, passwordless authentication: - -1. Generate or import SSH key -2. Add key to server's `~/.ssh/authorized_keys` -3. Select key in app when adding server - -See [SSH Keys](/configuration/ssh-keys/) for detailed setup. - -## Server Groups - -Organize servers into groups for easier management: - -1. Go to Settings > Server Groups -2. Create a new group -3. Assign servers to groups -4. Groups appear as sections in main view - -## Server Cards - -Customize what information appears on server cards: - -1. Go to Settings > Server Card Settings -2. Enable/disable metrics: - - CPU - - Memory - - Disk - - Network -3. Reorder cards by dragging - -## Connection Profiles - -Save connection profiles for different use cases: - -- **Default Profile**: Standard settings -- **Low Bandwidth**: Reduced refresh rate -- **High Performance**: Maximum refresh rate - -## Troubleshooting - -### Connection Refused - -- Check server is running -- Verify SSH port -- Check firewall rules - -### Authentication Failed - -- Verify username/password -- Check SSH key permissions -- Ensure SSH service is running - -### Timeout - -- Check network connectivity -- Increase timeout in settings -- Try different network diff --git a/docs/src/content/docs/configuration/sftp.md b/docs/src/content/docs/configuration/sftp.md deleted file mode 100644 index 5ef7a848..00000000 --- a/docs/src/content/docs/configuration/sftp.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -title: SFTP File Browser -description: File transfer and management via SFTP ---- - -Browse, edit, and transfer files on your servers with a built-in SFTP client. - -## Basic Usage - -### Opening SFTP - -1. Connect to server -2. Tap **Files** button on server page -3. Or from terminal: Tap **SFTP** button - -### Navigation - -- **Tap folder**: Enter directory -- **Tap file**: View/Edit/Download options -- **Back button**: Previous directory -- **Home button**: User's home directory -- **Goto button**: Jump to path with autocomplete - -## File Operations - -### Common Actions - -| Action | How | -|--------|-----| -| **Download** | Long-press file → Download | -| **Upload** | Folder icon → Select file | -| **Rename** | Long-press → Rename | -| **Delete** | Long-press → Delete | -| **Copy/Move** | Long-press → Select → Choose destination | -| **Permissions** | Tap file info → Edit permissions | - -### Permission Editor - -Unix permissions editor: - -- **3x3 Grid**: User/Group/Other × Read/Write/Execute -- **Numeric**: Direct input (755, 644, etc.) -- **Symbolic**: rwxr-xr-x format - -### Edit Files - -1. Tap file → Edit -2. Edit in built-in editor -3. Save → Upload back to server - -**Size limit:** Files up to 1 MB. For larger files, use the terminal with vim/nano instead. - -**Editor settings:** Settings → SFTP Editor -- Preferred editor (vim, nano, etc.) -- Close after save -- Soft wrap -- Syntax highlighting - -## Display Settings - -### Sort Order - -Settings → Sort By: -- Name (alphabetical) -- Size (largest first) -- Time (newest first) - -### Folders First - -Show directories before files: -Settings → Folders First - -### Hidden Files - -Show dotfiles (`.git`, `.bashrc`, etc.): -Settings → Show Hidden Files - -## Archive Support - -Extract common archive formats directly on your server. - -| Format | Variants | Command Required | -|--------|----------|------------------| -| .tar.gz | .tgz, .tar.Z | tar | -| .tar.bz2 | .tbz2, .tar.bz2 | tar | -| .tar.xz | .txz | tar | -| .zip | .zipx | unzip | -| .7z | - | 7z | -| .rar | - | unrar | - -**Note:** The corresponding command (`tar`, `unzip`, `7z`, `unrar`) must be installed on your server. These tools handle many sub-formats not listed above. - -## Quick Access - -### From Terminal - -Tap **SFTP** button to open current terminal directory in file browser. - -### Remember Last Path - -Automatically return to last visited directory: -Settings → SFTP Open Last Path - -## Troubleshooting - -### Permission Denied - -- Check user has read access to directory -- Verify directory permissions: `ls -la` -- Ensure SFTP is enabled in sshd_config - -### Slow Listing - -Large directories (1000+ items) use pagination for performance. - -### Can't Edit File - -File larger than 1 MB? Use terminal with vim/nano instead. diff --git a/docs/src/content/docs/configuration/terminal.md b/docs/src/content/docs/configuration/terminal.md deleted file mode 100644 index d784f3a1..00000000 --- a/docs/src/content/docs/configuration/terminal.md +++ /dev/null @@ -1,127 +0,0 @@ ---- -title: Terminal & SSH -description: SSH terminal setup and configuration ---- - -Complete SSH terminal access with full keyboard support and customizable interface. - -## Basic Setup - -### First Connection - -1. Add server with SSH credentials -2. Tap server card to connect -3. Accept host key fingerprint (first time only) -4. Terminal opens automatically - -### Virtual Keyboard (Mobile) - -Customizable virtual keyboard for terminal access: - -| Button | Function | -|--------|----------| -| **Ctrl, Alt, Shift** | Modifier keys (tap before other key) | -| **Esc, Tab** | Special characters | -| **Arrows** | Navigation | -| **F1-F12** | Function keys | -| **SFTP** | Open current directory in file browser | -| **Clipboard** | Copy selection / Paste clipboard | -| **Snippets** | Quick command execution | - -**Customize keyboard:** Settings → SSH Virtual Keys -- Enable/disable keys -- Reorder layout -- Add/remove buttons - -## Terminal Settings - -### Appearance - -**Font Size:** Settings → Terminal Font Size -- Affects all new sessions -- Typical range: 8-24 pixels - -**Colors:** Settings → Terminal Color -- Text color -- Background color & opacity -- Blur effect (iOS/macOS) -- Cursor color - -### Keyboard Type - -If you can't input certain characters: - -1. Settings → Keyboard Type -2. Switch to `visiblePassword` -3. Note: CJK input may not work after this change - -## Connection Management - -### Multi-Tab - -- **Desktop**: Ctrl+T (new), Ctrl+W (close) -- **Mobile**: Tap + button -- Sessions persist between app launches - -### Auto-Connect - -Set server to auto-connect on app open: -1. Server settings → Auto-Connect -2. Enable toggle - -### Jump Server - -Route through intermediate server: - -1. Add and configure jump server first -2. Target server settings → Select jump server -3. Connection routes through jump server automatically - -## SSH Keys (Recommended) - -More secure than passwords: - -1. Generate key: Settings → Private Keys → Add -2. Upload public key to server: `ssh-copy-id -i pubkey user@server` -3. Server settings → Use key instead of password - -## Common Issues - -### Can't Connect - -**Timeout/Refused:** -- Verify server is Unix-like (Linux, macOS, Android/Termux) -- Check firewall allows SSH port (default 22) -- Test manually: `ssh user@server -p port` - -**Auth Failed:** -- Verify username and password -- Check SSH key is uploaded correctly -- Ensure account is not locked - -### Terminal Disconnects - -**Frequent disconnections:** - -1. Check server keep-alive settings: - ```bash - # /etc/ssh/sshd_config - ClientAliveInterval 60 - ClientAliveCountMax 3 - ``` - -2. Disable battery optimization: - - **MIUI**: Battery → "No limits" - - **Android**: Settings → Apps → Disable optimization - - **iOS**: Enable background refresh - -### Can't Input Characters - -Change keyboard type to `visiblePassword` in settings. - -## Tips - -- **Test connection** first with regular SSH client -- **Use SSH keys** instead of passwords for security -- **Save snippets** for frequently used commands -- **Pinch to zoom** for temporary font size change (mobile) diff --git a/docs/src/content/docs/de/advanced/bulk-import.md b/docs/src/content/docs/de/advanced/bulk-import.md new file mode 100644 index 00000000..d603698a --- /dev/null +++ b/docs/src/content/docs/de/advanced/bulk-import.md @@ -0,0 +1,83 @@ +--- +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** (`keyId`) 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", + "keyId": "", + "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) | +| `keyId` | Nein | SSH-Schlüsselname (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", + "keyId": "my-key", + "tags": ["production", "web"] + }, + { + "name": "Entwicklung", + "ip": "dev.example.com", + "port": 2222, + "user": "dev", + "keyId": "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 diff --git a/docs/src/content/docs/de/advanced/custom-commands.md b/docs/src/content/docs/de/advanced/custom-commands.md new file mode 100644 index 00000000..718a5756 --- /dev/null +++ b/docs/src/content/docs/de/advanced/custom-commands.md @@ -0,0 +1,72 @@ +--- +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. diff --git a/docs/src/content/docs/de/advanced/custom-logo.md b/docs/src/content/docs/de/advanced/custom-logo.md new file mode 100644 index 00000000..75006005 --- /dev/null +++ b/docs/src/content/docs/de/advanced/custom-logo.md @@ -0,0 +1,54 @@ +--- +title: Benutzerdefiniertes Server-Logo +description: Verwenden Sie benutzerdefinierte Bilder für Serverkarten +--- + +Zeigen Sie benutzerdefinierte Logos auf Serverkarten mithilfe von Bild-URLs an. + +## Einrichtung + +1. Servereinstellungen → Benutzerdefiniertes Logo +2. Bild-URL eingeben + +## URL-Platzhalter + +### {DIST} - Linux-Distribution + +Wird automatisch durch die erkannte Distribution ersetzt: + +``` +https://example.com/{DIST}.png +``` + +Wird zu: `debian.png`, `ubuntu.png`, `arch.png`, usw. + +### {BRIGHT} - Theme + +Wird automatisch durch das aktuelle Theme ersetzt: + +``` +https://example.com/{BRIGHT}.png +``` + +Wird zu: `light.png` oder `dark.png` + +### Beide kombinieren + +``` +https://example.com/{DIST}-{BRIGHT}.png +``` + +Wird zu: `debian-light.png`, `ubuntu-dark.png`, usw. + +## Tipps + +- Verwenden Sie PNG- oder SVG-Formate +- Empfohlene Größe: 64x64 bis 128x128 Pixel +- Verwenden Sie HTTPS-URLs +- Halten Sie die Dateigrößen gering + +## Unterstützte Distributionen + +debian, ubuntu, centos, fedora, opensuse, kali, alpine, arch, rocky, deepin, armbian, wrt + +Vollständige Liste: [`dist.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/model/server/dist.dart) diff --git a/docs/src/content/docs/de/advanced/json-settings.md b/docs/src/content/docs/de/advanced/json-settings.md new file mode 100644 index 00000000..a454ff5f --- /dev/null +++ b/docs/src/content/docs/de/advanced/json-settings.md @@ -0,0 +1,64 @@ +--- +title: Versteckte Einstellungen (JSON) +description: Zugriff auf erweiterte Einstellungen über den JSON-Editor +--- + +Einige Einstellungen sind in der Benutzeroberfläche ausgeblendet, aber über den JSON-Editor zugänglich. + +## Zugriff + +Halten Sie **Einstellungen** in der Seitenleiste lange gedrückt, um den JSON-Editor zu öffnen. + +## Gängige versteckte Einstellungen + +### timeOut + +Verbindungs-Timeout in Sekunden. + +```json +{"timeOut": 10} +``` + +**Typ:** Integer | **Standard:** 5 | **Bereich:** 1-60 + +### recordHistory + +Verlauf speichern (SFTP-Pfade, usw.). + +```json +{"recordHistory": true} +``` + +**Typ:** Boolean | **Standard:** true + +### textFactor + +Textskalierungsfaktor. + +```json +{"textFactor": 1.2} +``` + +**Typ:** Double | **Standard:** 1.0 | **Bereich:** 0.8-1.5 + +## Weitere Einstellungen finden + +Alle Einstellungen sind in [`setting.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/store/setting.dart) definiert. + +Suchen Sie nach: +```dart +late final settingName = StoreProperty(box, 'settingKey', defaultValue); +``` + +## ⚠️ Wichtig + +**Vor dem Bearbeiten:** +- **Backup erstellen** - Falsche Einstellungen können dazu führen, dass die App nicht mehr öffnet +- **Sorgfältig bearbeiten** - JSON muss gültig sein + +## Wiederherstellung + +Wenn die App nach dem Bearbeiten nicht mehr öffnet: +1. App-Daten löschen (letzter Ausweg) +2. App neu installieren +3. Aus Backup wiederherstellen diff --git a/docs/src/content/docs/de/advanced/troubleshooting.md b/docs/src/content/docs/de/advanced/troubleshooting.md new file mode 100644 index 00000000..ac6d2265 --- /dev/null +++ b/docs/src/content/docs/de/advanced/troubleshooting.md @@ -0,0 +1,118 @@ +--- +title: Häufige Probleme +description: Lösungen für gängige Probleme +--- + +## Verbindungsprobleme + +### SSH verbindet nicht + +**Symptome:** Timeout, Verbindung abgelehnt, Authentifizierung fehlgeschlagen + +**Lösungen:** + +1. **Servertyp überprüfen:** Nur Unix-ähnliche Systeme werden unterstützt (Linux, macOS, Android/Termux) +2. **Manuell testen:** `ssh benutzer@server -p port` +3. **Firewall prüfen:** Port 22 muss offen sein +4. **Anmeldedaten prüfen:** Benutzername und Passwort/Schlüssel korrekt + +### Häufige Verbindungsabbrüche + +**Symptome:** Das Terminal trennt die Verbindung nach Inaktivität + +**Lösungen:** + +1. **Server Keep-Alive:** + ```bash + # /etc/ssh/sshd_config + ClientAliveInterval 60 + ClientAliveCountMax 3 + ``` + +2. **Akku-Optimierung deaktivieren:** + - MIUI: Akku → "Keine Beschränkungen" + - Android: Einstellungen → Apps → Optimierung deaktivieren + - iOS: Hintergrundaktualisierung aktivieren + +## Eingabeprobleme + +### Bestimmte Zeichen können nicht getippt werden + +**Lösung:** Einstellungen → Tastaturtyp → Wechseln zu `visiblePassword` + +Hinweis: CJK-Eingaben funktionieren nach dieser Änderung möglicherweise nicht mehr. + +## App-Probleme + +### App stürzt beim Start ab + +**Symptome:** App öffnet sich nicht, schwarzer Bildschirm + +**Ursachen:** Korrupte Einstellungen, insbesondere durch den JSON-Editor + +**Lösungen:** + +1. **App-Daten löschen:** + - Android: Einstellungen → Apps → ServerBox → Daten löschen + - iOS: Löschen und neu installieren + +2. **Backup wiederherstellen:** Importieren Sie ein Backup, das vor der Änderung der Einstellungen erstellt wurde + +### Probleme beim Sichern/Wiederherstellen + +**Backup funktioniert nicht:** +- Speicherplatz prüfen +- Sicherstellen, dass die App Speicherberechtigungen hat +- Anderen Speicherort versuchen + +**Wiederherstellung schlägt fehl:** +- Integrität der Backup-Datei prüfen +- Kompatibilität der App-Version prüfen + +## Widget-Probleme + +### Widget aktualisiert nicht + +**iOS:** +- Bis zu 30 Minuten auf automatische Aktualisierung warten +- Widget entfernen und neu hinzufügen +- Prüfen, ob die URL auf `/status` endet + +**Android:** +- Auf das Widget tippen, um die Aktualisierung zu erzwingen +- Sicherstellen, dass die Widget-ID mit der Konfiguration in den App-Einstellungen übereinstimmt + +**watchOS:** +- Watch-App neu starten +- Nach Konfigurationsänderung einige Minuten warten +- URL-Format prüfen + +### Widget zeigt Fehler + +- Sicherstellen, dass der ServerBox Monitor auf dem Server läuft +- URL im Browser testen +- Authentifizierungsdaten prüfen + +## Leistungsprobleme + +### App ist langsam + +**Lösungen:** +- Aktualisierungsrate in den Einstellungen reduzieren +- Netzwerkgeschwindigkeit prüfen +- Nicht verwendete Server deaktivieren + +### Hoher Akkuverbrauch + +**Lösungen:** +- Aktualisierungsintervalle vergrößern +- Hintergrundaktualisierung deaktivieren +- Nicht verwendete SSH-Sitzungen schließen + +## Hilfe erhalten + +Wenn die Probleme weiterhin bestehen: + +1. **GitHub Issues durchsuchen:** https://github.com/lollipopkit/flutter_server_box/issues +2. **Neues Issue erstellen:** App-Version, Plattform und Schritte zur Reproduktion angeben +3. **Wiki prüfen:** Diese Dokumentation und das GitHub Wiki diff --git a/docs/src/content/docs/de/advanced/widgets.md b/docs/src/content/docs/de/advanced/widgets.md new file mode 100644 index 00000000..ae49dba8 --- /dev/null +++ b/docs/src/content/docs/de/advanced/widgets.md @@ -0,0 +1,90 @@ +--- +title: Startbildschirm-Widgets +description: Fügen Sie Serverstatus-Widgets zu Ihrem Startbildschirm hinzu +--- + +Erfordert [ServerBox Monitor](https://github.com/lollipopkit/server_box_monitor) auf Ihren Servern installiert. + +## Voraussetzungen + +Installieren Sie zuerst ServerBox Monitor auf Ihrem Server. Anweisungen zur Einrichtung finden Sie im [ServerBox Monitor Wiki](https://github.com/lollipopkit/server_box_monitor/wiki/Home). + +Nach der Installation sollte Ihr Server verfügen über: +- Einen HTTP/HTTPS-Endpunkt +- Einen `/status` API-Endpunkt +- Optionale Authentifizierung + +## URL-Format + +``` +https://ihr-server.com/status +``` + +Muss auf `/status` enden. + +## iOS-Widget + +### Einrichtung + +1. Startbildschirm lange drücken → Auf **+** tippen +2. Nach "ServerBox" suchen +3. Widget-Größe wählen +4. Widget lange drücken → **Widget bearbeiten** +5. URL eingeben, die auf `/status` endet + +### Hinweise + +- Muss HTTPS verwenden (außer bei lokalen IPs) +- Maximale Aktualisierungsrate: 30 Minuten (iOS-Limit) +- Fügen Sie mehrere Widgets für mehrere Server hinzu + +## Android-Widget + +### Einrichtung + +1. Startbildschirm lange drücken → **Widgets** +2. "ServerBox" finden → Zum Startbildschirm hinzufügen +3. Notieren Sie sich die angezeigte Widget-ID-Nummer +4. ServerBox-App öffnen → Einstellungen +5. Tippen Sie auf **Config home widget link** +6. Eintrag hinzufügen: `Widget ID` = `Status-URL` + +Beispiel: +- Key: `17` +- Value: `https://mein-server.com/status` + +7. Tippen Sie auf das Widget auf dem Startbildschirm, um es zu aktualisieren + +## watchOS-Widget + +### Einrichtung + +1. iPhone-App öffnen → Einstellungen +2. **iOS-Einstellungen** → **Watch-App** +3. Auf **URL hinzufügen** tippen +4. URL eingeben, die auf `/status` endet +5. Warten, bis die Watch-App synchronisiert ist + +### Hinweise + +- Versuchen Sie, die Watch-App neu zu starten, wenn sie nicht aktualisiert wird +- Sicherstellen, dass Telefon und Watch verbunden sind + +## Fehlerbehebung + +### Widget aktualisiert nicht + +**iOS:** Warten Sie bis zu 30 Minuten, dann entfernen Sie es und fügen es erneut hinzu. +**Android:** Tippen Sie auf das Widget, um die Aktualisierung zu erzwingen, überprüfen Sie die ID in den Einstellungen. +**watchOS:** Starten Sie die Watch-App neu, warten Sie einige Minuten. + +### Widget zeigt Fehler an + +- Sicherstellen, dass ServerBox Monitor läuft +- URL im Browser testen +- Prüfen, ob die URL auf `/status` endet + +## Sicherheit + +- **Verwenden Sie immer HTTPS**, wann immer möglich +- **Lokale IPs nur** in vertrauenswürdigen Netzwerken diff --git a/docs/src/content/docs/de/development/architecture.md b/docs/src/content/docs/de/development/architecture.md new file mode 100644 index 00000000..4e0a4841 --- /dev/null +++ b/docs/src/content/docs/de/development/architecture.md @@ -0,0 +1,86 @@ +--- +title: Architektur +description: Architekturmuster und Designentscheidungen +--- + +Server Box folgt den Prinzipien der Clean Architecture mit einer klaren Trennung zwischen Daten-, Domänen- und Präsentationsschicht. + +## Schichtarchitektur + +``` +┌─────────────────────────────────────┐ +│ Präsentationsschicht │ +│ (lib/view/page/) │ +│ - Seiten, Widgets, Controller │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Business-Logik-Schicht │ +│ (lib/data/provider/) │ +│ - Riverpod Provider │ +│ - Zustandsverwaltung │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Datenschicht │ +│ (lib/data/model/, store/) │ +│ - Modelle, Speicher, Dienste │ +└─────────────────────────────────────┘ +``` + +## Schlüsselmuster + +### Zustandsverwaltung: Riverpod + +- **Codegenerierung**: Verwendet `riverpod_generator` für typsichere Provider +- **State Notifier**: Für veränderlichen Zustand mit Business-Logik +- **Async Notifier**: Für Lade- und Fehlerzustände +- **Stream Provider**: Für Echtzeitdaten + +### Unveränderliche Modelle: Freezed + +- Alle Datenmodelle verwenden Freezed für Unveränderlichkeit +- Union-Typen zur Darstellung von Zuständen +- Integrierte JSON-Serialisierung +- CopyWith-Erweiterungen für Aktualisierungen + +### Lokale Speicherung: Hive + +- **hive_ce**: Community-Edition von Hive +- Keine manuellen `@HiveField` oder `@HiveType` erforderlich +- Typ-Adapter werden automatisch generiert +- Persistenter Key-Value-Speicher + +## Dependency Injection + +Dienste und Stores werden injiziert über: + +1. **Provider**: Stellen Abhängigkeiten der UI zur Verfügung +2. **GetIt**: Service-Locator (wo anwendbar) +3. **Konstruktor-Injektion**: Explizite Abhängigkeiten + +## Datenfluss + +``` +Benutzeraktion → Widget → Provider → Dienst/Store → Modell-Update → UI-Neuaufbau +``` + +1. Benutzer interagiert mit Widget +2. Widget ruft Provider-Methode auf +3. Provider aktualisiert Zustand über Dienst/Store +4. Zustandsänderung löst Neuaufbau der UI aus +5. Neuer Zustand spiegelt sich im Widget 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 diff --git a/docs/src/content/docs/de/development/building.md b/docs/src/content/docs/de/development/building.md new file mode 100644 index 00000000..64c93c74 --- /dev/null +++ b/docs/src/content/docs/de/development/building.md @@ -0,0 +1,116 @@ +--- +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 +``` + +## Produktions-Build + +Das Projekt verwendet `fl_build` zum Bauen: + +```bash +# Für eine bestimmte Plattform bauen +dart run fl_build -p + +# Verfügbare Plattformen: +# - ios +# - android +# - macos +# - linux +# - windows +``` + +## Plattformspezifische Builds + +### iOS + +```bash +dart run fl_build -p ios +``` + +Erfordert: +- macOS mit Xcode +- CocoaPods +- Apple Developer Account für die Signierung + +### Android + +```bash +dart run fl_build -p android +``` + +Erfordert: +- Android SDK +- Java Development Kit +- Keystore für die Signierung + +### macOS + +```bash +dart run fl_build -p macos +``` + +### Linux + +```bash +dart run fl_build -p linux +``` + +### Windows + +```bash +dart run fl_build -p windows +``` + +Erfordert Windows mit Visual Studio. + +## Vor/Nach dem Build + +Das Skript `make.dart` übernimmt: + +- Metadaten-Generierung +- Aktualisierung der Versions-Strings +- Plattformspezifische Konfigurationen + +## Fehlerbehebung + +### Clean Build + +```bash +flutter clean +dart run build_runner build --delete-conflicting-outputs +flutter pub get +``` + +### Versions-Konflikt + +Stellen Sie sicher, dass alle Abhängigkeiten kompatibel sind: +```bash +flutter pub upgrade +``` + +## Release-Checkliste + +1. Version in `pubspec.yaml` aktualisieren +2. Codegenerierung ausführen +3. Tests ausführen +4. Für alle Zielplattformen bauen +5. Auf physischen Geräten testen +6. GitHub-Release erstellen diff --git a/docs/src/content/docs/de/development/codegen.md b/docs/src/content/docs/de/development/codegen.md new file mode 100644 index 00000000..40dbdcbc --- /dev/null +++ b/docs/src/content/docs/de/development/codegen.md @@ -0,0 +1,98 @@ +--- +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 + +# Bereinigen und neu generieren +dart run build_runner build --delete-conflicting-outputs --clean +``` + +## 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; +} +``` + +### 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 json) => + _$ServerFromJson(json); + Map toJson() => _$ServerToJson(this); +} +``` + +### Riverpod Provider (`*.g.dart`) + +Generiert aus der `@riverpod` Annotation: + +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; +} +``` + +### Hive-Adapter (`*.g.dart`) + +Automatisch generiert für Hive-Modelle (hive_ce): + +```dart +@HiveType(typeId: 0) +class ServerModel { + @HiveField(0) + final String id; +} +``` + +## Generierung der Lokalisierung + +```bash +flutter gen-l10n +``` + +Generiert `lib/generated/l10n/` aus `lib/l10n/*.arb` Dateien. + +## Tipps + +- Verwenden Sie `--delete-conflicting-outputs`, um Konflikte zu vermeiden. +- Fügen Sie generierte Dateien zur `.gitignore` hinzu. +- Bearbeiten Sie generierte Dateien niemals manuell. diff --git a/docs/src/content/docs/de/development/state.md b/docs/src/content/docs/de/development/state.md new file mode 100644 index 00000000..af8d4833 --- /dev/null +++ b/docs/src/content/docs/de/development/state.md @@ -0,0 +1,115 @@ +--- +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; + } +} +``` + +### AsyncNotifierProvider + +Zustand, der asynchron mit Lade-/Fehlerzuständen geladen wird: + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + return fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => fetchStatus(server)); + } +} +``` + +### StreamProvider + +Echtzeitdaten aus Streams: + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + return cpuService.monitor(server); +} +``` + +## Zustandsmuster + +### Ladezustände + +```dart +state.when( + data: (data) => DataWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### Family Provider + +Parametrisierte Provider: + +```dart +@riverpod +List containers(ContainersRef ref, Server server) { + return containerService.list(server); +} +``` + +### Auto-Dispose + +Provider, die verworfen werden, wenn sie nicht mehr referenziert werden: + +```dart +@Riverpod(keepAlive: false) +class TempState extends _$TempState { + // ... +} +``` + +## Best Practices + +1. **Codegenerierung nutzen**: Immer die `@riverpod` Annotation verwenden. +2. **Provider lokal platzieren**: In der Nähe der Widgets platzieren, die sie nutzen. +3. **Singletons vermeiden**: Stattdessen Provider verwenden. +4. **Korrekt schichten**: UI-Logik von Business-Logik getrennt halten. + +## Zustand in Widgets lesen + +```dart +class ServerWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final status = ref.watch(serverStatusProvider(server)); + return status.when(...); + } +} +``` + +## Zustand ändern + +```dart +ref.read(settingsProvider.notifier).update(newSettings); +``` diff --git a/docs/src/content/docs/de/development/structure.md b/docs/src/content/docs/de/development/structure.md new file mode 100644 index 00000000..36425647 --- /dev/null +++ b/docs/src/content/docs/de/development/structure.md @@ -0,0 +1,96 @@ +--- +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 +``` + +## 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 diff --git a/docs/src/content/docs/de/development/testing.md b/docs/src/content/docs/de/development/testing.md new file mode 100644 index 00000000..0172c3ed --- /dev/null +++ b/docs/src/content/docs/de/development/testing.md @@ -0,0 +1,113 @@ +--- +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 +``` + +## Teststruktur + +Tests befinden sich im Verzeichnis `test/` und spiegeln die Struktur von `lib/` wider: + +``` +test/ +├── data/ +│ ├── model/ +│ └── provider/ +├── view/ +│ └── widget/ +└── test_helpers.dart +``` + +## Unit-Tests + +Geschäftslogik und Datenmodelle testen: + +```dart +test('sollte CPU-Prozentsatz berechnen', () { + final cpu = CpuModel(usage: 75.0); + expect(cpu.usagePercentage, '75%'); +}); +``` + +## 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); +}); +``` + +## 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()); +}); +``` + +## Mocking + +Mocks für externe Abhängigkeiten verwenden: + +```dart +class MockSshService extends Mock implements SshService {} + +test('verbindet zum Server', () async { + final mockSsh = MockSshService(); + when(mockSsh.connect(any)).thenAnswer((_) async => true); + + // Test mit Mock +}); +``` + +## Integrationstests + +Komplette Benutzerabläufe testen (in `integration_test/`): + +```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. diff --git a/docs/src/content/docs/de/index.mdx b/docs/src/content/docs/de/index.mdx new file mode 100644 index 00000000..04631f9b --- /dev/null +++ b/docs/src/content/docs/de/index.mdx @@ -0,0 +1,46 @@ +--- +title: Server Box +description: Eine umfassende plattformübergreifende Server-Management-Anwendung +hero: + tagline: Verwalten Sie Ihre Linux-Server von überall aus + actions: + - text: Loslegen + link: /de/introduction/ + icon: right-arrow + variant: primary + - text: Auf GitHub ansehen + link: https://github.com/lollipopkit/flutter_server_box + icon: github + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +## Funktionen + + + + Überwachen Sie CPU, Arbeitsspeicher, Festplatte, Netzwerk, GPU und Temperatur mit ansprechenden Echtzeit-Diagrammen. + + + Voll ausgestattetes SSH-Terminal mit Multi-Tab-Unterstützung und virtueller Tastatur für mobile Geräte. + + + Verwalten Sie Dateien auf Ihren Servern mit dem integrierten SFTP-Client und dem lokalen Dateibrowser. + + + Starten, stoppen und überwachen Sie Docker-Container mit einer intuitiven Benutzeroberfläche. + + + Verfügbar für iOS, Android, macOS, Linux, Windows und watchOS. + + + Vollständige Lokalisierungsunterstützung inklusive Englisch, Chinesisch, Deutsch, Französisch und mehr. + + + +## Quick-Links + +- **Download**: Verfügbar im [App Store](https://apps.apple.com/app/id1586449703), auf [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) und bei [F-Droid](https://f-droid.org/) +- **Dokumentation**: Entdecken Sie die Anleitungen für den Einstieg in die Server Box +- **Support**: Treten Sie unserer Community auf GitHub für Diskussionen und Probleme bei diff --git a/docs/src/content/docs/de/installation.mdx b/docs/src/content/docs/de/installation.mdx new file mode 100644 index 00000000..fa48ef79 --- /dev/null +++ b/docs/src/content/docs/de/installation.mdx @@ -0,0 +1,51 @@ +--- +title: Installation +description: Laden Sie Server Box herunter und installieren Sie es auf Ihrem Gerät +--- + +Server Box ist für mehrere Plattformen verfügbar. Wählen Sie Ihre bevorzugte Installationsmethode. + +## Mobile Apps + +### iOS + +Laden Sie es aus dem **[App Store](https://apps.apple.com/app/id1586449703)** herunter. + +### Android + +Wählen Sie Ihre bevorzugte Quelle: + +- **[F-Droid](https://f-droid.org/)** – Für Benutzer, die reine FOSS-Quellen bevorzugen +- **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** – Für die neueste Version direkt von der Quelle + +## Desktop Apps + +### macOS + +Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +Funktionen: +- Native Menüleisten-Integration +- Unterstützung für sowohl Intel als auch Apple Silicon + +### Linux + +Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +Verfügbar als AppImage, deb oder tar.gz Pakete. + +### Windows + +Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +## watchOS + +Verfügbar im **[App Store](https://apps.apple.com/app/id1586449703)** als Teil der iOS-App. + +## Aus dem Quellcode bauen + +Um Server Box aus dem Quellcode zu bauen, lesen Sie den Abschnitt [Bauen](/de/development/building/) in der Entwicklungsdokumentation. + +## Versionsinformationen + +Besuchen Sie die Seite [GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases) für die neueste Version und das Änderungsprotokoll. diff --git a/docs/src/content/docs/de/introduction.mdx b/docs/src/content/docs/de/introduction.mdx new file mode 100644 index 00000000..22aa00f8 --- /dev/null +++ b/docs/src/content/docs/de/introduction.mdx @@ -0,0 +1,32 @@ +--- +title: Einführung +description: Erfahren Sie, was Server Box ist und was es kann +--- + +Server Box ist eine umfassende plattformübergreifende Server-Management-Anwendung, die mit Flutter entwickelt wurde. Sie ermöglicht es Ihnen, Ihre Linux-, Unix- und Windows-Server von überall aus zu überwachen, zu verwalten und zu steuern. + +## Was ist Server Box? + +Server Box bietet eine einheitliche Oberfläche für Server-Administrationsaufgaben über SSH-Verbindungen. Egal, ob Sie Systemadministrator, Entwickler oder Hobbyist mit eigenen Heimservern sind – diese App bietet Ihnen leistungsstarke Server-Management-Tools direkt in Ihrer Tasche. + +## Kernfunktionen + +- **Echtzeit-Überwachung**: Verfolgen Sie CPU, Arbeitsspeicher, Festplattenbelegung, Netzwerkgeschwindigkeit, GPU-Status und Systemtemperaturen. +- **SSH-Terminal**: Voller Terminalzugriff mit Multi-Tab-Unterstützung und anpassbarem Erscheinungsbild. +- **SFTP-Client**: Durchsuchen und verwalten Sie Dateien auf Ihren Servern. +- **Docker-Verwaltung**: Steuern Sie Container mit Leichtigkeit. +- **Prozess-Management**: Systemprozesse anzeigen und verwalten. +- **Systemd-Dienste**: Systemd-Dienste starten, stoppen und überwachen. +- **Netzwerk-Tools**: iPerf-Tests, Ping und Wake-on-LAN. +- **Snippets**: Benutzerdefinierte Shell-Befehle speichern und ausführen. + +## Unterstützte Plattformen + +Server Box ist wahrhaft plattformübergreifend: + +- **Mobil**: iOS und Android +- **Desktop**: macOS, Linux und Windows + +## Lizenz + +Dieses Projekt ist unter der AGPL v3 lizenziert. Der Quellcode ist auf [GitHub](https://github.com/lollipopkit/flutter_server_box) verfügbar. diff --git a/docs/src/content/docs/de/platforms/desktop.md b/docs/src/content/docs/de/platforms/desktop.md new file mode 100644 index 00000000..38eaf535 --- /dev/null +++ b/docs/src/content/docs/de/platforms/desktop.md @@ -0,0 +1,80 @@ +--- +title: Desktop-Funktionen +description: Spezifische Funktionen für macOS, Linux und Windows +--- + +Server Box bietet auf Desktop-Plattformen zusätzliche Produktivitätsfunktionen. + +## macOS + +### Menüleisten-Integration + +- Schneller Serverstatus in der Menüleiste +- Serverzugriff mit einem Klick +- Kompaktmodus für minimale Ablenkung +- Natives macOS-Menüleistenstyling + +### Beständigkeit des Fensterzustands + +- Merkt sich Fensterposition und -größe +- Wiederherstellung der vorherigen Sitzung beim Start +- Unterstützung für mehrere Monitore + +### Native Funktionen + +- **Titelleiste**: Option für benutzerdefinierte oder System-Titelleiste +- **Vollbildmodus**: Dedizierte Serverüberwachung +- **Tastenkombinationen**: macOS-native Tastenkürzel +- **Touch Bar** (unterstützte Geräte): Schnellaktionen + +## Linux + +### Native Integration + +- Unterstützung für System-Tray +- Integration von Desktop-Benachrichtigungen +- Integration der Dateiauswahl + +### Fensterverwaltung + +- Unterstützung für X11 und Wayland +- Freundlich gegenüber Tiling-Window-Managern +- Option für benutzerdefinierte Fensterdekorationen + +## Windows + +### Funktionen + +- System-Tray-Integration +- Jump List Schnellaktionen +- Native Fenstersteuerung +- Option für Autostart beim Booten + +## Plattformübergreifende Desktop-Funktionen + +### Tastenkombinationen + +- **Cmd/Ctrl + N**: Neuer Server +- **Cmd/Ctrl + W**: Tab schließen +- **Cmd/Ctrl + T**: Neuer Terminal-Tab +- **Cmd/Ctrl + ,**: Einstellungen + +### Themes + +- Helles Theme +- Dunkles Theme +- AMOLED Theme (reines Schwarz) +- System-Theme (folgt dem Betriebssystem) + +### Mehrere Fenster + +- Öffnen mehrerer Server in separaten Fenstern +- Tabs in ein neues Fenster ziehen +- Serverstatistiken Seite an Seite vergleichen + +### Vorteile gegenüber Mobile + +- Größerer Bildschirm für die Überwachung +- Volle Tastatur für das Terminal +- Schnellere Dateioperationen +- Besseres Multitasking diff --git a/docs/src/content/docs/de/platforms/mobile.md b/docs/src/content/docs/de/platforms/mobile.md new file mode 100644 index 00000000..680b0673 --- /dev/null +++ b/docs/src/content/docs/de/platforms/mobile.md @@ -0,0 +1,77 @@ +--- +title: Mobile Funktionen +description: Spezifische Funktionen für iOS und Android +--- + +Server Box bietet mehrere mobile-spezifische Funktionen für iOS- und Android-Geräte. + +## Biometrische Authentifizierung + +Sichern Sie Ihre Server mit biometrischer Authentifizierung: + +- **iOS**: Face ID oder Touch ID +- **Android**: Fingerabdruck-Authentifizierung + +Aktivieren Sie dies unter Einstellungen > Sicherheit > Biometrische Authentifizierung. + +## Startbildschirm-Widgets + +Fügen Sie Serverstatus-Widgets zu Ihrem Startbildschirm für eine schnelle Überwachung hinzu. + +### iOS + +- Auf den Startbildschirm lange drücken +- Auf **+** tippen, um ein Widget hinzuzufügen +- Nach "Server Box" suchen +- Widget-Größe wählen: + - Klein: Status eines einzelnen Servers + - Mittel: Mehrere Server + - Groß: Detaillierte Informationen + +### Android + +- Auf den Startbildschirm lange drücken +- Auf **Widgets** tippen +- "Server Box" finden +- Widget-Typ auswählen + +## Hintergrundbetrieb + +### Android + +Verbindungen im Hintergrund aufrechterhalten: + +- Aktivieren unter Einstellungen > Erweitert > Hintergrundbetrieb +- Erfordert Ausschluss von der Akku-Optimierung +- Permanente Benachrichtigungen für aktive Verbindungen + +### iOS + +Es gelten Hintergrundbeschränkungen: + +- Verbindungen können im Hintergrund pausieren +- Schnelle Wiederverbindung bei Rückkehr zur App +- Unterstützung für Hintergrundaktualisierung + +## Push-Benachrichtigungen + +Erhalten Sie Benachrichtigungen für: + +- Server-Offline-Alarme +- Warnungen bei hoher Ressourcenauslastung +- Alarme bei Abschluss von Aufgaben + +Konfigurieren unter Einstellungen > Benachrichtigungen. + +## Mobile UI-Funktionen + +- **Pull to Refresh**: Serverstatus aktualisieren +- **Wischgesten**: Schnelle Serveroperationen +- **Querformat**: Besseres Terminal-Erlebnis +- **Virtuelle Tastatur**: Terminal-Shortcuts + +## Datei-Integration + +- **Dateien-App (iOS)**: Direkter SFTP-Zugriff aus Dateien +- **Storage Access Framework (Android)**: Dateien mit anderen Apps teilen +- **Dokumentenauswahl**: Einfache Dateiauswahl diff --git a/docs/src/content/docs/de/principles/architecture.md b/docs/src/content/docs/de/principles/architecture.md new file mode 100644 index 00000000..9c6e59dd --- /dev/null +++ b/docs/src/content/docs/de/principles/architecture.md @@ -0,0 +1,214 @@ +--- +title: Architektur-Übersicht +description: High-Level-Anwendungsarchitektur +--- + +Server Box folgt einer Schichtarchitektur mit klarer Trennung der Belange (Separation of Concerns). + +## Architektur-Schichten + +``` +┌─────────────────────────────────────────────────┐ +│ Präsentationsschicht (UI) │ +│ lib/view/page/, lib/view/widget/ │ +│ - Seiten, Widgets, Controller │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Business-Logik-Schicht │ +│ lib/data/provider/ │ +│ - Riverpod Provider, State Notifier │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Datenzugriffsschicht │ +│ lib/data/store/, lib/data/model/ │ +│ - Hive Stores, Datenmodelle │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Externe Integrationsschicht │ +│ - SSH (dartssh2), Terminal (xterm), SFTP │ +│ - Plattformspezifischer Code (iOS, Android etc.)│ +└─────────────────────────────────────────────────┘ +``` + +## Anwendungsgrundlagen + +### Haupteinstiegspunkt + +`lib/main.dart` initialisiert die App: + +```dart +void main() { + runApp( + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +### Root-Widget + +`MyApp` bietet: +- **Theme-Management**: Umschalten zwischen hellem/dunklem Theme +- **Routing-Konfiguration**: Navigationsstruktur +- **Provider Scope**: Wurzel für Dependency Injection + +### Startseite + +`HomePage` dient als Navigationszentrum: +- **Tab-Interface**: Server, Snippet, Container, SSH +- **Zustandsverwaltung**: Zustand pro Tab +- **Navigation**: Funktionszugriff + +## Kernsysteme + +### Zustandsverwaltung: Riverpod + +**Warum Riverpod?** +- Sicherheit zur Kompilierzeit +- Einfache Testbarkeit +- Keine Abhängigkeit vom Build-Kontext +- Funktioniert plattformübergreifend + +**Verwendete Provider-Typen:** +- `StateProvider`: Einfacher veränderlicher Zustand +- `AsyncNotifierProvider`: Lade-/Fehler-/Datenzustände +- `StreamProvider`: Echtzeit-Datenströme +- Future Provider: Einmalige asynchrone Operationen + +### Datenpersistenz: Hive CE + +**Warum Hive CE?** +- Keine Abhängigkeiten von nativem Code +- Schneller Key-Value-Speicher +- Typsicher durch Codegenerierung +- Keine manuellen Feld-Annotationen erforderlich + +**Stores:** +- `SettingStore`: App-Einstellungen +- `ServerStore`: Server-Konfigurationen +- `SnippetStore`: Befehls-Snippets +- `KeyStore`: SSH-Schlüssel + +### Immutable Modelle: Freezed + +**Vorteile:** +- Unveränderlichkeit zur Kompilierzeit +- Union Types für Zustände +- Integrierte JSON-Serialisierung +- CopyWith-Erweiterungen + +## Cross-Plattform-Strategie + +### Plugin-System + +Flutter-Plugins ermöglichen die Plattformintegration: + +| Plattform | Integrationsmethode | +|-----------|--------------------| +| iOS | CocoaPods, Swift/Obj-C | +| Android | Gradle, Kotlin/Java | +| macOS | CocoaPods, Swift | +| Linux | CMake, C++ | +| Windows | CMake, C# | + +### Plattformspezifische Funktionen + +**Nur iOS:** +- Startbildschirm-Widgets +- Live-Aktivitäten +- Apple Watch Begleit-App + +**Nur Android:** +- Hintergrunddienst +- Push-Benachrichtigungen +- Dateisystemzugriff + +**Nur Desktop:** +- Menüleisten-Integration +- Mehrere Fenster +- Benutzerdefinierte Titelleiste + +## Eigene Abhängigkeiten + +### dartssh2 Fork + +Erweiterter SSH-Client mit: +- Besserer mobiler Unterstützung +- Verbesserter Fehlerbehandlung +- Leistungsoptimierungen + +### xterm.dart Fork + +Terminal-Emulator mit: +- Für Mobilgeräte optimiertem Rendering +- Unterstützung für Touch-Gesten +- Integration der virtuellen Tastatur + +### fl_lib + +Paket mit gemeinsamen Dienstprogrammen: +- Gemeinsame Widgets +- Erweiterungen +- Hilfsfunktionen + +## Build-System + +### fl_build Paket + +Eigenes Build-System für: +- Multi-Plattform-Builds +- Code-Signierung +- Asset-Bündelung +- Versionsverwaltung + +### Build-Prozess + +``` +make.dart (Version) → fl_build (Build) → Plattform-Output +``` + +1. **Pre-build**: Berechnung der Version aus Git +2. **Build**: Kompilierung für die Zielplattform +3. **Post-build**: Paketierung und Signierung + +## Beispiel für den Datenfluss + +### Aktualisierung des Serverstatus + +``` +1. Timer löst aus → +2. Provider ruft Service auf → +3. Service führt SSH-Befehl aus → +4. Antwort wird in Modell geparst → +5. Zustand wird aktualisiert → +6. UI wird mit neuen Daten neu aufgebaut +``` + +### Ablauf einer Benutzeraktion + +``` +1. Benutzer tippt auf Schaltfläche → +2. Widget ruft Provider-Methode auf → +3. Provider aktualisiert Zustand → +4. Zustandsänderung löst Neuaufbau aus → +5. Neuer Zustand spiegelt sich in der UI wider +``` + +## Sicherheitsarchitektur + +### Datenschutz + +- **Passwörter**: Verschlüsselt mit flutter_secure_storage +- **SSH-Schlüssel**: Verschlüsselt gespeichert +- **Host-Fingerabdrücke**: Sicher gespeichert +- **Sitzungsdaten**: Werden nicht persistiert + +### Verbindungssicherheit + +- **Host-Key-Verifizierung**: MITM-Erkennung +- **Verschlüsselung**: Standard-SSH-Verschlüsselung +- **Kein Klartext**: Sensible Daten werden niemals im Klartext gespeichert diff --git a/docs/src/content/docs/de/principles/sftp.md b/docs/src/content/docs/de/principles/sftp.md new file mode 100644 index 00000000..daa21800 --- /dev/null +++ b/docs/src/content/docs/de/principles/sftp.md @@ -0,0 +1,490 @@ +--- +title: SFTP-System +description: Wie der SFTP-Dateibrowser funktioniert +--- + +Das SFTP-System bietet Dateimanagement-Funktionen über SSH. + +## Architektur + +``` +┌─────────────────────────────────────────────┐ +│ SFTP UI Schicht │ +│ - Dateibrowser (remote) │ +│ - Dateibrowser (lokal) │ +│ - Transfer-Warteschlange │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SFTP-Zustandsverwaltung │ +│ - sftpProvider │ +│ - Pfadverwaltung │ +│ - Operations-Warteschlange │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SFTP-Protokollschicht │ +│ - SSH-Subsystem │ +│ - Dateioperationen │ +│ - Verzeichnisauflistung │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SSH-Transport │ +│ - Sicherer Kanal │ +│ - Daten-Streaming │ +└─────────────────────────────────────────────┘ +``` + +## Verbindungsaufbau + +### Erstellung des SFTP-Clients + +```dart +Future createSftpClient(Spi spi) async { + // 1. SSH-Client abrufen (wiederverwenden, falls verfügbar) + final sshClient = await genClient(spi); + + // 2. SFTP-Subsystem öffnen + final sftp = await sshClient.openSftp(); + + return sftp; +} +``` + +### Wiederverwendung von Verbindungen + +SFTP verwendet bestehende SSH-Verbindungen wieder: + +```dart +class ServerProvider { + SSHClient? _sshClient; + SftpClient? _sftpClient; + + Future getSftpClient(String spiId) async { + _sftpClient ??= await _sshClient!.openSftp(); + return _sftpClient!; + } +} +``` + +## Dateisystem-Operationen + +### Verzeichnisauflistung + +```dart +Future> listDirectory(String path) async { + final sftp = await getSftpClient(spiId); + + // Verzeichnis auflisten + final files = await sftp.listDir(path); + + // Basierend auf Einstellungen sortieren + 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); + } + }); + + // Ordner zuerst, falls aktiviert + if (showFoldersFirst) { + final dirs = files.where((f) => f.isDirectory); + final regular = files.where((f) => !f.isDirectory); + return [...dirs, ...regular]; + } + + return files; +} +``` + +### Dateimetadaten + +```dart +class SftpFile { + final String name; + final String path; + final int size; // Bytes + final int modified; // Unix-Zeitstempel + final String permissions; // z.B. "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); +} +``` + +## Dateioperationen + +### Hochladen + +```dart +Future uploadFile( + String localPath, + String remotePath, +) async { + final sftp = await getSftpClient(spiId); + + // Anfrage erstellen + final req = SftpReq( + spi: spi, + remotePath: remotePath, + localPath: localPath, + type: SftpReqType.upload, + ); + + // Zur Warteschlange hinzufügen + _transferQueue.add(req); + + // Transfer mit Fortschritt ausführen + 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); + }, + ); + + // Fertigstellen + _transferQueue.remove(req); +} +``` + +### Herunterladen + +```dart +Future downloadFile( + String remotePath, + String localPath, +) async { + final sftp = await getSftpClient(spiId); + + // Lokale Datei erstellen + final file = File(localPath); + final sink = file.openWrite(); + + // Herunterladen mit Fortschritt + final stat = await sftp.stat(remotePath); + + await sftp.download( + fromPath: remotePath, + toSink: sink, + onProgress: (transferred) { + _updateProgress( + SftpReq(...), + transferred, + stat.size, + ); + }, + ); + + await sink.close(); +} +``` + +### Berechtigungen bearbeiten + +```dart +Future setPermissions( + String path, + String permissions, +) async { + final sftp = await getSftpClient(spiId); + + // Berechtigungen parsen (z.B. "rwxr-xr-x" oder "755") + final mode = parsePermissions(permissions); + + // Über SSH-Befehl setzen (zuverlässiger als SFTP) + final ssh = await getSshClient(spiId); + await ssh.exec('chmod $mode "$path"'); +} +``` + +## Pfadverwaltung + +### Pfadstruktur + +```dart +class PathWithPrefix { + final String prefix; // z.B. "/home/user" + final String path; // Relativ oder absolut + + String get fullPath { + if (path.startsWith('/')) { + return path; // Absoluter Pfad + } + return '$prefix/$path'; // Relativer Pfad + } + + PathWithPrefix cd(String subPath) { + return PathWithPrefix( + prefix: fullPath, + path: subPath, + ); + } +} +``` + +### Navigationsverlauf + +```dart +class PathHistory { + final List _history = []; + int _index = -1; + + void push(String path) { + // Vorwärtsverlauf entfernen + _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; + } +} +``` + +## Transfersystem + +### Transfer-Anfrage + +```dart +class SftpReq { + final Spi spi; + final String remotePath; + final String localPath; + final SftpReqType type; + final DateTime createdAt; + + int? totalBytes; + int? transferredBytes; + String? error; +} +``` + +### Fortschrittsverfolgung + +```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); + } +} +``` + +### Warteschlangen-Management + +```dart +class TransferQueue { + final List _queue = []; + final Map _progress = {}; + int _concurrent = 3; // Max. gleichzeitige Transfers + + Future 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 _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); + } + } +} +``` + +## Lokales Speichermuster + +### Download-Cache + +Heruntergeladene Dateien werden gespeichert unter: + +```dart +String getLocalDownloadPath(String spiId, String remotePath) { + final normalized = remotePath.replaceAll('/', '_'); + return 'Paths.file/$spiId/$normalized'; +} +``` + +Beispiel: +- Remote: `/var/log/nginx/access.log` +- spiId: `server-123` +- Lokal: `Paths.file/server-123/_var_log_nginx_access.log` + +## Dateibearbeitung + +### Bearbeitungs-Workflow + +```dart +Future editFile(String path) async { + final sftp = await getSftpClient(spiId); + + // 1. Größe prüfen + final stat = await sftp.stat(path); + if (stat.size > editorMaxSize) { + showWarning('Datei zu groß für den integrierten Editor'); + return; + } + + // 2. Temporär herunterladen + final temp = await downloadToTemp(path); + + // 3. Im Editor öffnen + final content = await openEditor(temp.path); + + // 4. Zurück hochladen + await uploadFile(temp.path, path); + + // 5. Aufräumen + await temp.delete(); +} +``` + +### Integration externer Editoren + +```dart +Future editInExternalEditor(String path) async { + final ssh = await getSshClient(spiId); + + // Terminal mit Editor öffnen + final editor = getSetting('sftpEditor', 'vim'); + await ssh.exec('$editor "$path"'); + + // Benutzer bearbeitet im Terminal + // Nach dem Speichern die SFTP-Ansicht aktualisieren +} +``` + +## Fehlerbehandlung + +### Berechtigungsfehler + +```dart +try { + await sftp.upload(...); +} on SftpPermissionException { + showError('Berechtigung verweigert: ${stat.path}'); + showHint('Prüfen Sie Dateiberechtigungen und Eigentümerschaft'); +} +``` + +### Verbindungsfehler + +```dart +try { + await sftp.listDir(path); +} on SftpConnectionException { + showError('Verbindung verloren'); + await reconnect(); +} +``` + +### Speicherplatzfehler + +```dart +try { + await sftp.upload(...); +} on SftpNoSpaceException { + showError('Festplatte auf dem Remote-Server voll'); +} +``` + +## Leistungsoptimierungen + +### Verzeichnis-Caching + +```dart +class DirectoryCache { + final Map _cache = {}; + final Duration ttl = Duration(minutes: 5); + + Future> 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; + } +} +``` + +### Lazy Loading + +Für große Verzeichnisse (>1000 Einträge): + +```dart +List 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)); +} +``` + +### Paginierung + +```dart +class PaginatedDirectory { + static const pageSize = 100; + + Future> getPage(int page) async { + final offset = page * pageSize; + return await sftp.listDir( + path, + offset: offset, + limit: pageSize, + ); + } +} +``` diff --git a/docs/src/content/docs/de/principles/ssh.md b/docs/src/content/docs/de/principles/ssh.md new file mode 100644 index 00000000..928122c1 --- /dev/null +++ b/docs/src/content/docs/de/principles/ssh.md @@ -0,0 +1,305 @@ +--- +title: SSH-Verbindung +description: Wie SSH-Verbindungen aufgebaut und verwaltet werden +--- + +Verständnis der SSH-Verbindungen in Server Box. + +## Verbindungsablauf + +```text +Benutzereingabe → Spi-Konfiguration → genClient() → SSH-Client → Sitzung +``` + +### Schritt 1: Konfiguration + +Das `Spi` (Server Parameter Info) Modell enthält: + +```dart +class Spi { + String id; // eindeutige ID / unique identifier + String name; // Servername + String ip; // IP-Adresse + int port; // SSH-Port (Standard 22) + String user; // Benutzername + String? pwd; // Passwort (verschlüsselt) + String? keyId; // SSH-Schlüssel-ID + String? jumpId; // Jump-Server-ID + String? alterUrl; // Alternative URL +} +``` + +### Schritt 2: Client-Generierung + +`genClient(spi)` erstellt den SSH-Client: + +```dart +Future genClient(Spi spi) async { + // 1. Socket aufbauen + var socket = await connect(spi.ip, spi.port); + + // 2. Alternative URL versuchen, falls fehlgeschlagen + if (socket == null && spi.alterUrl != null) { + socket = await connect(spi.alterUrl, spi.port); + } + + if (socket == null) { + throw ConnectionException('Unable to connect'); + } + + // 3. Authentifizieren + final client = SSHClient( + socket: socket, + username: spi.user, + onPasswordRequest: () => spi.pwd, + onIdentityRequest: () => loadKey(spi.keyId), + ); + + // 4. Host-Key verifizieren + await verifyHostKey(client, spi); + + return client; +} +``` + +### Schritt 3: Jump-Server (falls konfiguriert) + +Für Jump-Server, rekursive Verbindung: + +```dart +if (spi.jumpId != null) { + final jumpClient = await genClient(getJumpSpi(spi.jumpId)); + final forwarded = await jumpClient.forwardLocal( + spi.ip, + spi.port, + ); + // Über weitergeleiteten Socket verbinden +} +``` + +## Authentifizierungsmethoden + +### Passwort-Authentifizierung + +```dart +onPasswordRequest: () => spi.pwd +``` + +- Passwort verschlüsselt in Hive gespeichert +- Bei Verbindung entschlüsselt +- Zur Verifizierung an den Server gesendet + +### Private-Key-Authentifizierung + +```dart +onIdentityRequest: () async { + final key = await KeyStore.get(spi.keyId); + return decyptPem(key.pem, key.password); +} +``` + +**Schlüssel-Ladeprozess:** +1. Verschlüsselten Schlüssel aus `KeyStore` abrufen +2. Passwort entschlüsseln (Biometrie/Eingabeaufforderung) +3. PEM-Format parsen +4. Zeilenenden standardisieren (LF) +5. Zur Authentifizierung zurückgeben + +### Tastatur-Interaktiv (Keyboard-Interactive) + +```dart +onUserInfoRequest: (instructions) async { + // Challenge-Response handhaben + return responses; +} +``` + +Unterstützt: +- Passwort-Authentifizierung +- OTP-Token +- Zwei-Faktor-Authentifizierung (2FA) + +## Host-Key-Verifizierung + +### Warum Host-Keys verifizieren? + +Verhindert **Man-in-the-Middle (MITM)** Angriffe, indem sichergestellt wird, dass Sie sich mit demselben Server verbinden. + +### Speicherformat + +```text +{spi.id}::{keyType} +``` + +Beispiel: +```text +mein-server::ssh-ed25519 +mein-server::ecdsa-sha2-nistp256 +``` + +### Fingerabdruck-Formate + +**MD5 Hex:** +```text +aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99 +``` + +**Base64:** +```text +SHA256:AbCdEf1234567890...= +``` + +### Verifizierungsablauf + +```dart +Future verifyHostKey(SSHClient client, Spi spi) async { + final key = await client.hostKey; + final keyType = key.type; + final fingerprint = md5Hex(key); // oder base64 + + final stored = SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType']; + + if (stored == null) { + // Neuer Host - Benutzer fragen + final trust = await promptUser( + 'Unbekannter Host', + 'Fingerabdruck: $fingerprint', + ); + if (trust) { + SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType'] = fingerprint; + } + } else if (stored != fingerprint) { + // Geändert - Benutzer warnen + await warnUser( + 'Host-Key geändert!', + 'Möglicher MITM-Angriff', + ); + } +} +``` + +## Sitzungsverwaltung + +### Verbindungs-Pooling + +Aktive Clients werden im `ServerProvider` verwaltet: + +```dart +class ServerProvider { + final Map _clients = {}; + + SSHClient getClient(String spiId) { + return _clients[spiId] ??= connect(spiId); + } +} +``` + +### Keep-Alive + +Verbindung bei Inaktivität aufrechterhalten: + +```dart +Timer.periodic( + Duration(seconds: 30), + (_) => client.sendKeepAlive(), +); +``` + +### Automatische Wiederverbindung + +Bei Verbindungsverlust: + +```dart +client.onError.listen((error) async { + await Future.delayed(Duration(seconds: 5)); + reconnect(); +}); +``` + +## Lebenszyklus einer Verbindung + +```text +┌─────────────┐ +│ Initial │ +└──────┬──────┘ + │ connect() + ↓ +┌─────────────┐ +│ Verbinden │ ←──┐ +└──────┬──────┘ │ + │ Erfolg │ + ↓ │ Fehler (Retry) +┌─────────────┐ │ +│ Verbunden │───┘ +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ Aktiv │ ──→ Befehle senden +└──────┬──────┘ + │ + ↓ (Fehler/Trennung) +┌─────────────┐ +│ Getrennt │ +└─────────────┘ +``` + +## Fehlerbehandlung + +### Verbindungs-Timeout + +```dart +try { + await client.connect().timeout( + Duration(seconds: 30), + ); +} on TimeoutException { + throw ConnectionException('Verbindungs-Timeout'); +} +``` + +### Authentifizierungsfehler + +```dart +onAuthFail: (error) { + if (error.contains('password')) { + return 'Ungültiges Passwort'; + } else if (error.contains('key')) { + return 'Ungültiger SSH-Schlüssel'; + } + return 'Authentifizierung fehlgeschlagen'; +} +``` + +### Host-Key-Abweichung + +```dart +onHostKeyMismatch: (stored, current) { + showSecurityWarning( + 'Host-Key hat sich geändert!', + 'Möglicher MITM-Angriff', + ); +} +``` + +## Leistungsaspekte + +### Wiederverwendung von Verbindungen + +- Wiederverwendung von Clients über Funktionen hinweg +- Nicht unnötig trennen/wiederverbinden +- Verbindungs-Pooling für gleichzeitige Operationen + +### Optimale Einstellungen + +- **Timeout**: 30 Sekunden (anpassbar) +- **Keep-Alive**: Alle 30 Sekunden +- **Wiederholungsverzögerung**: 5 Sekunden + +### Netzwerkeffizienz + +- Einzelne Verbindung für mehrere Operationen +- Befehle pipelinen, wenn möglich +- Das Öffnen mehrerer Verbindungen vermeiden diff --git a/docs/src/content/docs/de/principles/state.md b/docs/src/content/docs/de/principles/state.md new file mode 100644 index 00000000..3e737068 --- /dev/null +++ b/docs/src/content/docs/de/principles/state.md @@ -0,0 +1,221 @@ +--- +title: Zustandsverwaltung +description: Wie der Zustand mit Riverpod verwaltet wird +--- + +Verständnis der Architektur zur Zustandsverwaltung in Server Box. + +## Warum Riverpod? + +**Hauptvorteile:** +- **Sicherheit zur Kompilierzeit**: Fehler werden bereits beim Kompilieren abgefangen +- **Kein BuildContext erforderlich**: Zugriff auf den Zustand von überall aus +- **Einfache Testbarkeit**: Provider können leicht isoliert getestet werden +- **Codegenerierung**: Weniger Boilerplate, typsicher + +## Provider-Architektur + +``` +┌─────────────────────────────────────────────┐ +│ UI-Schicht (Widgets) │ +│ - ConsumerWidget / ConsumerStatefulWidget │ +│ - ref.watch() / ref.read() │ +└─────────────────────────────────────────────┘ + ↓ beobachtet (watches) +┌─────────────────────────────────────────────┐ +│ Provider-Schicht │ +│ - @riverpod Annotationen │ +│ - Generierte *.g.dart Dateien │ +└─────────────────────────────────────────────┘ + ↓ nutzt (uses) +┌─────────────────────────────────────────────┐ +│ Service- / Store-Schicht │ +│ - Business-Logik │ +│ - Datenzugriff │ +└─────────────────────────────────────────────┘ +``` + +## Verwendete Provider-Typen + +### 1. StateProvider (Einfacher Zustand) + +Für einfachen, beobachtbaren Zustand: + +```dart +@riverpod +class ThemeNotifier extends _$ThemeNotifier { + @override + ThemeMode build() { + // Aus Einstellungen laden + return SettingStore.themeMode; + } + + void setTheme(ThemeMode mode) { + state = mode; + SettingStore.themeMode = mode; // Persistieren + } +} +``` + +**Verwendung:** +```dart +class MyWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themeNotifierProvider); + return Text('Theme: $theme'); + } +} +``` + +### 2. AsyncNotifierProvider (Asynchroner Zustand) + +Für Daten, die asynchron geladen werden: + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + // Initiales Laden + return await fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + return await fetchStatus(server); + }); + } +} +``` + +**Verwendung:** +```dart +final status = ref.watch(serverStatusProvider(server)); + +status.when( + data: (data) => StatusWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 3. StreamProvider (Echtzeit-Daten) + +Für kontinuierliche Datenströme: + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + final client = ref.watch(sshClientProvider(server)); + final stream = client.monitorCpu(); + + // Ressourcen freigeben, wenn nicht mehr beobachtet + ref.onDispose(() { + client.stopMonitoring(); + }); + + return stream; +} +``` + +**Verwendung:** +```dart +final cpu = ref.watch(cpuUsageProvider(server)); + +cpu.when( + data: (usage) => CpuChart(usage), + loading: () => CircularProgressIndicator(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 4. Family Provider (Parametrisiert) + +Provider, die Parameter akzeptieren: + +```dart +@riverpod +Future> containers(ContainersRef ref, Server server) async { + final client = await ref.watch(sshClientProvider(server).future); + return await client.listContainers(); +} +``` + +**Verwendung:** +```dart +final containers = ref.watch(containersProvider(server)); + +// Verschiedene Server = verschiedene gecachte Zustände +final containers2 = ref.watch(containersProvider(server2)); +``` + +## Muster für Zustandsaktualisierungen + +### Direkte Zustandsaktualisierung + +```dart +ref.read(settingsProvider.notifier).updateTheme(darkMode); +``` + +### Berechneter Zustand (Computed State) + +```dart +@riverpod +int totalServers(TotalServersRef ref) { + final servers = ref.watch(serversProvider); + return servers.length; +} +``` + +### Abgeleiteter Zustand (Derived State) + +```dart +@riverpod +List onlineServers(OnlineServersRef ref) { + final all = ref.watch(serversProvider); + return all.where((s) => s.isOnline).toList(); +} +``` + +## Server-spezifischer Zustand + +### Pro-Server Provider + +Jeder Server hat einen isolierten Zustand: + +```dart +@riverpod +class ServerProvider extends _$ServerProvider { + @override + ServerState build(Server server) { + return ServerState.disconnected(); + } + + Future connect() async { + state = ServerState.connecting(); + try { + final client = await genClient(server.spi); + state = ServerState.connected(client); + } catch (e) { + state = ServerState.error(e.toString()); + } + } +} +``` + +## Leistungsoptimierungen + +- **Provider Keep-Alive**: `@Riverpod(keepAlive: true)` verwenden, um automatische Entsorgung ohne Listener zu verhindern. +- **Selektives Beobachten**: `select` verwenden, um nur bestimmte Teile des Zustands zu beobachten. +- **Provider Caching**: Family Provider cachen Ergebnisse pro Parameter. + +## Best Practices + +1. **Provider lokal platzieren**: In der Nähe der Widgets, die sie nutzen. +2. **Codegenerierung nutzen**: Immer `@riverpod` verwenden. +3. **Provider fokussiert halten**: Jedes Provider sollte nur eine Aufgabe haben. +4. **Ladezustände behandeln**: AsyncValue-Zustände immer vollständig behandeln. +5. **Ressourcen entsorgen**: `ref.onDispose()` für Aufräumarbeiten nutzen. +6. **Tiefe Provider-Bäume vermeiden**: Den Provider-Graphen flach halten. diff --git a/docs/src/content/docs/de/principles/terminal.md b/docs/src/content/docs/de/principles/terminal.md new file mode 100644 index 00000000..21164bfd --- /dev/null +++ b/docs/src/content/docs/de/principles/terminal.md @@ -0,0 +1,198 @@ +--- +title: Terminal-Implementierung +description: Wie das SSH-Terminal intern funktioniert +--- + +Das SSH-Terminal ist eine der komplexesten Funktionen, aufgebaut auf einem benutzerdefinierten xterm.dart-Fork. + +## Architektur-Übersicht + +``` +┌─────────────────────────────────────────────┐ +│ Terminal UI Schicht │ +│ - Tab-Management │ +│ - Virtuelle Tastatur │ +│ - Textauswahl │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ xterm.dart Emulator │ +│ - PTY (Pseudo Terminal) │ +│ - VT100/ANSI Emulation │ +│ - Rendering-Engine │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SSH-Client-Schicht │ +│ - SSH-Sitzung │ +│ - Kanalverwaltung │ +│ - Daten-Streaming │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Remote-Server │ +│ - Shell-Prozess │ +│ - Befehlsausführung │ +└─────────────────────────────────────────────┘ +``` + +## Lebenszyklus einer Terminal-Sitzung + +### 1. Sitzungserstellung + +```dart +Future createSession(Spi spi) async { + // 1. SSH-Client abrufen + final client = await genClient(spi); + + // 2. PTY erstellen + final pty = await client.openPty( + term: 'xterm-256color', + cols: 80, + rows: 24, + ); + + // 3. Terminal-Emulator initialisieren + final terminal = Terminal( + backend: PtyBackend(pty), + ); + + // 4. Resize-Handler einrichten + terminal.onResize.listen((size) { + pty.resize(size.cols, size.rows); + }); + + return TerminalSession( + terminal: terminal, + pty: pty, + client: client, + ); +} +``` + +### 2. Terminal-Emulation + +Der xterm.dart-Fork bietet: + +**VT100/ANSI Emulation:** +- Cursor-Bewegung +- Farben (256-Farben-Unterstützung) +- Textattribute (fett, unterstrichen, usw.) +- Scroll-Bereiche +- Alternativer Bildschirmpuffer + +**Rendering:** +- Zeilenbasiertes Rendering +- Unterstützung für bidirektionalen Text +- Unicode/Emoji Unterstützung +- Optimierte Redraws + +### 3. Datenfluss + +``` +Benutzereingabe + ↓ +Virtuelle Tastatur / Physische Tastatur + ↓ +Terminal-Emulator (Taste → Escape-Sequenz) + ↓ +SSH-Kanal (senden) + ↓ +Remote PTY + ↓ +Remote Shell + ↓ +Befehlsausgabe + ↓ +SSH-Kanal (empfangen) + ↓ +Terminal-Emulator (Analyse von ANSI-Codes) + ↓ +Rendering auf dem Bildschirm +``` + +## Multi-Tab System + +### Tab-Management + +Tabs behalten ihren Zustand bei Navigationswechseln bei: +- SSH-Verbindung bleibt aktiv +- Terminalzustand bleibt erhalten +- Scroll-Puffer bleibt bestehen +- Eingabeverlauf bleibt erhalten + +## Virtuelle Tastatur + +### Plattformspezifische Implementierung + +**iOS:** +- UIView-basierte benutzerdefinierte Tastatur +- Umschaltbar mit Tastatur-Button +- Automatisches Ein-/Ausblenden basierend auf dem Fokus + +**Android:** +- Benutzerdefinierte Eingabemethode +- Integriert in die Systemtastatur +- Schnellaktionstasten + +### Tastatur-Buttons + +| Button | Aktion | +|--------|--------| +| **Umschalten** | Systemtastatur ein-/ausblenden | +| **Ctrl** | Ctrl-Modifikator senden | +| **Alt** | Alt-Modifikator senden | +| **SFTP** | Aktuelles Verzeichnis öffnen | +| **Zwischenablage** | Kontextsensitive Kopieren/Einfügen | +| **Snippets** | Snippet ausführen | + +## Textauswahl + +1. **Langes Drücken**: Auswahlmodus aktivieren +2. **Ziehen**: Auswahl erweitern +3. **Loslassen**: In die Zwischenablage kopieren + +## Schriftart und Dimensionen + +### Größenberechnung + +```dart +class TerminalDimensions { + static Size calculate(double fontSize, Size screenSize) { + final charWidth = fontSize * 0.6; // Monospace-Seitenverhältnis + final charHeight = fontSize * 1.2; + + final cols = (screenSize.width / charWidth).floor(); + final rows = (screenSize.height / charHeight).floor(); + + return Size(cols.toDouble(), rows.toDouble()); + } +} +``` + +### Pinch-to-Zoom + +```dart +GestureDetector( + onScaleStart: () => _baseFontSize = currentFontSize, + onScaleUpdate: (details) { + final newFontSize = _baseFontSize * details.scale; + resize(newFontSize); + }, +) +``` + +## Farbschema + +- **Hell (Light)**: Heller Hintergrund, dunkler Text +- **Dunkel (Dark)**: Dunkler Hintergrund, heller Text +- **AMOLED**: Rein schwarzer Hintergrund + +## Leistungsoptimierungen + +- **Dirty Rectangle**: Nur geänderte Regionen neu zeichnen +- **Zeilen-Caching**: Gerenderte Zeilen cachen +- **Lazy Scrolling**: Virtuelles Scrollen für lange Puffer +- **Batch-Updates**: Mehrere Schreibvorgänge zusammenfassen +- **Kompression**: Kompression des Scroll-Puffers +- **Debouncing**: Debouncing für schnelle Eingaben diff --git a/docs/src/content/docs/de/quick-start.mdx b/docs/src/content/docs/de/quick-start.mdx new file mode 100644 index 00000000..222a191e --- /dev/null +++ b/docs/src/content/docs/de/quick-start.mdx @@ -0,0 +1,45 @@ +--- +title: Schnellstart +description: In wenigen Minuten mit Server Box loslegen +--- + +Folgen Sie dieser Schnellstartanleitung, um sich mit Ihrem ersten Server zu verbinden und mit der Überwachung zu beginnen. + +## Schritt 1: Einen Server hinzufügen + +1. Öffnen Sie Server Box +2. Tippen Sie auf die Schaltfläche **+**, um einen neuen Server hinzuzufügen +3. Geben Sie die Serverinformationen ein: + - **Name**: Ein Anzeigename für Ihren Server + - **Host**: IP-Adresse oder Domainname + - **Port**: SSH-Port (Standard: 22) + - **Benutzer**: SSH-Benutzername + - **Passwort oder Schlüssel**: Authentifizierungsmethode + +4. Tippen Sie auf **Speichern**, um den Server hinzuzufügen + +## Schritt 2: Verbinden und Überwachen + +1. Tippen Sie auf Ihre Serverkarte, um die Verbindung herzustellen +2. Die App baut eine SSH-Verbindung auf +3. Sie sehen den Echtzeit-Status für: + - CPU-Auslastung + - Arbeitsspeicher (RAM) und Swap + - Festplattenbelegung + - Netzwerkgeschwindigkeit + +## Schritt 3: Funktionen erkunden + +Sobald die Verbindung hergestellt ist, können Sie: + +- **Terminal öffnen**: Tippen Sie auf die Terminal-Schaltfläche für vollen SSH-Zugriff +- **Dateien durchsuchen**: Verwenden Sie SFTP, um Dateien zu verwalten +- **Container verwalten**: Docker-Container anzeigen und steuern +- **Prozesse anzeigen**: Laufende Prozesse überprüfen +- **Snippets ausführen**: Gespeicherte Befehle ausführen + +## Tipps + +- **Biometrische Authentifizierung**: Aktivieren Sie Face ID / Touch ID / Fingerabdruck für schnellen Zugriff (Mobilgerät) +- **Startbildschirm-Widgets**: Fügen Sie Serverstatus-Widgets zu Ihrem Startbildschirm hinzu (iOS/Android) +- **Hintergrundbetrieb**: Halten Sie Verbindungen im Hintergrund aktiv (Android) diff --git a/docs/src/content/docs/development/architecture.md b/docs/src/content/docs/development/architecture.md index 475395a7..6fb4837a 100644 --- a/docs/src/content/docs/development/architecture.md +++ b/docs/src/content/docs/development/architecture.md @@ -3,7 +3,7 @@ title: Architecture description: Architecture patterns and design decisions --- -Flutter Server Box follows clean architecture principles with clear separation between data, domain, and presentation layers. +Server Box follows clean architecture principles with clear separation between data, domain, and presentation layers. ## Layered Architecture diff --git a/docs/src/content/docs/development/building.md b/docs/src/content/docs/development/building.md index cf989c38..2c98a917 100644 --- a/docs/src/content/docs/development/building.md +++ b/docs/src/content/docs/development/building.md @@ -3,7 +3,7 @@ title: Building description: Build instructions for different platforms --- -Flutter Server Box uses a custom build system (`fl_build`) for cross-platform builds. +Server Box uses a custom build system (`fl_build`) for cross-platform builds. ## Prerequisites diff --git a/docs/src/content/docs/development/codegen.md b/docs/src/content/docs/development/codegen.md index 67bce6eb..1e714d00 100644 --- a/docs/src/content/docs/development/codegen.md +++ b/docs/src/content/docs/development/codegen.md @@ -3,7 +3,7 @@ title: Code Generation description: Using build_runner for code generation --- -Flutter Server Box heavily uses code generation for models, state management, and serialization. +Server Box heavily uses code generation for models, state management, and serialization. ## When to Run Code Generation diff --git a/docs/src/content/docs/development/state.md b/docs/src/content/docs/development/state.md index 8d85fbe9..1522615c 100644 --- a/docs/src/content/docs/development/state.md +++ b/docs/src/content/docs/development/state.md @@ -3,7 +3,7 @@ title: State Management description: Riverpod-based state management patterns --- -Flutter Server Box uses Riverpod with code generation for state management. +Server Box uses Riverpod with code generation for state management. ## Provider Types diff --git a/docs/src/content/docs/development/structure.md b/docs/src/content/docs/development/structure.md index ec31caa9..dbf5e11d 100644 --- a/docs/src/content/docs/development/structure.md +++ b/docs/src/content/docs/development/structure.md @@ -1,9 +1,9 @@ --- title: Project Structure -description: Understanding the Flutter Server Box codebase +description: Understanding the Server Box codebase --- -The Flutter Server Box project follows a modular architecture with clear separation of concerns. +The Server Box project follows a modular architecture with clear separation of concerns. ## Directory Structure diff --git a/docs/src/content/docs/es/advanced/bulk-import.md b/docs/src/content/docs/es/advanced/bulk-import.md new file mode 100644 index 00000000..ac5adec2 --- /dev/null +++ b/docs/src/content/docs/es/advanced/bulk-import.md @@ -0,0 +1,83 @@ +--- +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** (`keyId`) 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", + "keyId": "", + "tags": ["production"], + "autoConnect": false + } +] +``` + +## Campos + +| Campo | Requerido | Descripción | +|-------|-----------|-------------| +| `name` | Sí | Nombre para mostrar | +| `ip` | Sí | Dominio o dirección IP | +| `port` | Sí | Puerto SSH (usualmente 22) | +| `user` | Sí | Usuario SSH | +| `pwd` | No | Contraseña (evitar - usar claves SSH en su lugar) | +| `keyId` | No | Nombre de la clave SSH (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", + "keyId": "mi-clave", + "tags": ["production", "web"] + }, + { + "name": "Desarrollo", + "ip": "dev.example.com", + "port": 2222, + "user": "dev", + "keyId": "dev-clave", + "tags": ["development"] + } +] +``` + +## Consejos + +- **Usa claves SSH** en lugar de contraseñas cuando sea posible +- **Prueba la conexión** después de la importación +- **Organiza con etiquetas** para una gestión más sencilla +- **Elimina el archivo JSON** después de la importación +- **Nunca subas** archivos JSON con credenciales al control de versiones diff --git a/docs/src/content/docs/es/advanced/custom-commands.md b/docs/src/content/docs/es/advanced/custom-commands.md new file mode 100644 index 00000000..3b518b5d --- /dev/null +++ b/docs/src/content/docs/es/advanced/custom-commands.md @@ -0,0 +1,72 @@ +--- +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" +} +``` + +**Ejemplo:** +```json +{ + "Memoria": "free -h", + "Disco": "df -h", + "Tiempo de actividad": "uptime" +} +``` + +## Ver Resultados + +Tras la configuración, los comandos personalizados aparecerán en la página de detalles del servidor y se actualizarán automáticamente. + +## Nombres de Comando Especiales + +### server_card_top_right + +Se muestra en la tarjeta del servidor de la página de inicio (esquina superior derecha): + +```json +{ + "server_card_top_right": "tu-comando-aquí" +} +``` + +## Consejos + +**Usa rutas absolutas:** +```json +{"Mi Script": "/usr/local/bin/mi-script.sh"} +``` + +**Comandos con tuberías (pipes):** +```json +{"Proceso principal": "ps aux | sort -rk 3 | head -5"} +``` + +**Formatear salida:** +```json +{"Carga de CPU": "uptime | awk -F'load average:' '{print $2}'"} +``` + +**Mantén los comandos rápidos:** Menos de 5 segundos para una mejor experiencia. + +**Limitar salida:** +```json +{"Logs": "tail -20 /var/log/syslog"} +``` + +## Seguridad + +Los comandos se ejecutan con los permisos del usuario SSH. Evita comandos que modifiquen el estado del sistema. diff --git a/docs/src/content/docs/es/advanced/custom-logo.md b/docs/src/content/docs/es/advanced/custom-logo.md new file mode 100644 index 00000000..a0dbfa60 --- /dev/null +++ b/docs/src/content/docs/es/advanced/custom-logo.md @@ -0,0 +1,54 @@ +--- +title: Logo de Servidor Personalizado +description: Usa imágenes personalizadas para las tarjetas de servidor +--- + +Muestra logos personalizados en las tarjetas de servidor mediante URLs de imagen. + +## Configuración + +1. Ajustes del servidor → Logo personalizado +2. Introduce la URL de la imagen + +## Marcadores de posición de URL + +### {DIST} - Distribución Linux + +Se reemplaza automáticamente por la distribución detectada: + +``` +https://ejemplo.com/{DIST}.png +``` + +Se convierte en: `debian.png`, `ubuntu.png`, `arch.png`, etc. + +### {BRIGHT} - Tema + +Se reemplaza automáticamente por el tema actual: + +``` +https://ejemplo.com/{BRIGHT}.png +``` + +Se convierte en: `light.png` o `dark.png` + +### Combinar ambos + +``` +https://ejemplo.com/{DIST}-{BRIGHT}.png +``` + +Se convierte en: `debian-light.png`, `ubuntu-dark.png`, etc. + +## Consejos + +- Usa formatos PNG o SVG +- Tamaño recomendado: de 64x64 a 128x128 píxeles +- Usa URLs HTTPS +- Mantén tamaños de archivo pequeños + +## Distribuciones Soportadas + +debian, ubuntu, centos, fedora, opensuse, kali, alpine, arch, rocky, deepin, armbian, wrt + +Lista completa: [`dist.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/model/server/dist.dart) diff --git a/docs/src/content/docs/es/advanced/json-settings.md b/docs/src/content/docs/es/advanced/json-settings.md new file mode 100644 index 00000000..ef36cc88 --- /dev/null +++ b/docs/src/content/docs/es/advanced/json-settings.md @@ -0,0 +1,64 @@ +--- +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} +``` + +**Tipo:** entero | **Predeterminado:** 5 | **Rango:** 1-60 + +### recordHistory + +Guardar historial (rutas SFTP, etc.). + +```json +{"recordHistory": true} +``` + +**Tipo:** booleano | **Predeterminado:** true + +### textFactor + +Factor de escala de texto. + +```json +{"textFactor": 1.2} +``` + +**Tipo:** doble | **Predeterminado:** 1.0 | **Rango:** 0.8-1.5 + +## Encontrar Más Ajustes + +Todos los ajustes están definidos en [`setting.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/store/setting.dart). + +Busca: +```dart +late final settingName = StoreProperty(box, 'settingKey', defaultValue); +``` + +## ⚠️ Importante + +**Antes de editar:** +- **Crea una copia de seguridad**: unos ajustes incorrectos pueden hacer que la app no se abra +- **Edita con cuidado**: el JSON debe ser válido + +## Recuperación + +Si la aplicación no se abre tras editar: +1. Borra los datos de la aplicación (último recurso) +2. Reinstala la aplicación +3. Restaura desde una copia de seguridad diff --git a/docs/src/content/docs/es/advanced/troubleshooting.md b/docs/src/content/docs/es/advanced/troubleshooting.md new file mode 100644 index 00000000..48c4fdf9 --- /dev/null +++ b/docs/src/content/docs/es/advanced/troubleshooting.md @@ -0,0 +1,118 @@ +--- +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 + ``` + +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 diff --git a/docs/src/content/docs/es/advanced/widgets.md b/docs/src/content/docs/es/advanced/widgets.md new file mode 100644 index 00000000..5ccf2a3d --- /dev/null +++ b/docs/src/content/docs/es/advanced/widgets.md @@ -0,0 +1,90 @@ +--- +title: Widgets de Pantalla de Inicio +description: Añade widgets de estado del servidor a tu pantalla de inicio +--- + +Requiere tener instalado [ServerBox Monitor](https://github.com/lollipopkit/server_box_monitor) en tus servidores. + +## Requisitos Previos + +Instala primero ServerBox Monitor en tu servidor. Consulta la [Wiki de ServerBox Monitor](https://github.com/lollipopkit/server_box_monitor/wiki/Home) para ver las instrucciones de configuración. + +Tras la instalación, tu servidor debería tener: +- Un punto de acceso (endpoint) HTTP/HTTPS +- El punto de acceso API `/status` +- Autenticación opcional + +## Formato de URL + +``` +https://tu-servidor.com/status +``` + +Debe terminar en `/status`. + +## Widget de iOS + +### Configuración + +1. Mantén pulsada la pantalla de inicio → Toca el símbolo **+** +2. Busca "ServerBox" +3. Elige el tamaño del widget +4. Mantén pulsado el widget → **Editar widget** +5. Introduce la URL terminada en `/status` + +### Notas + +- Debe usar HTTPS (excepto IPs locales) +- Tasa máxima de refresco: 30 minutos (límite de iOS) +- Añade varios widgets para varios servidores + +## Widget de Android + +### Configuración + +1. Mantén pulsada la pantalla de inicio → **Widgets** +2. Busca "ServerBox" → Añadir a la pantalla de inicio +3. Anota el número de ID del widget que aparece +4. Abre la app ServerBox → Ajustes +5. Toca en **Configurar enlace de widget de inicio** +6. Añade la entrada: `Widget ID` = `URL de estado` + +Ejemplo: +- Clave (Key): `17` +- Valor (Value): `https://mi-servidor.com/status` + +7. Toca el widget en la pantalla de inicio para refrescarlo + +## Widget de watchOS + +### Configuración + +1. Abre la app en el iPhone → Ajustes +2. **Ajustes de iOS** → **App del Watch** +3. Toca en **Añadir URL** +4. Introduce la URL terminada en `/status` +5. Espera a que la app del reloj se sincronice + +### Notas + +- Prueba a reiniciar la app del reloj si no se actualiza +- Verifica que el teléfono y el reloj están conectados + +## Solución de Problemas + +### El Widget no se actualiza + +**iOS:** Espera hasta 30 minutos, luego elimínalo y vuelve a añadirlo. +**Android:** Toca el widget para forzar el refresco, verifica el ID en los ajustes. +**watchOS:** Reinicia la app del reloj, espera unos minutos. + +### El Widget muestra un error + +- Verifica que ServerBox Monitor se está ejecutando +- Prueba la URL en un navegador +- Comprueba que la URL termina en `/status` + +## Seguridad + +- **Usa siempre HTTPS** cuando sea posible +- **IPs locales solo** en redes de confianza diff --git a/docs/src/content/docs/es/development/architecture.md b/docs/src/content/docs/es/development/architecture.md new file mode 100644 index 00000000..f74931ca --- /dev/null +++ b/docs/src/content/docs/es/development/architecture.md @@ -0,0 +1,86 @@ +--- +title: Arquitectura +description: Patrones de arquitectura y decisiones de diseño +--- + +Server Box sigue los principios de Clean Architecture con una clara separación entre las capas de datos, dominio y presentación. + +## Arquitectura por Capas + +``` +┌─────────────────────────────────────┐ +│ Capa de Presentación │ +│ (lib/view/page/) │ +│ - Páginas, Widgets, Controladores │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Capa de Lógica de Negocio │ +│ (lib/data/provider/) │ +│ - Riverpod Providers │ +│ - Gestión de Estado │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Capa de Datos │ +│ (lib/data/model/, store/) │ +│ - Modelos, Almacén, Servicios │ +└─────────────────────────────────────┘ +``` + +## Patrones Clave + +### Gestión de Estado: Riverpod + +- **Generación de Código**: Usa `riverpod_generator` para providers con tipado seguro +- **State Notifiers**: Para estados mutables con lógica de negocio +- **Async Notifiers**: Para estados de carga y error +- **Stream Providers**: Para datos en tiempo real + +### Modelos Inmutables: Freezed + +- Todos los modelos de datos usan Freezed para inmutabilidad +- Tipos Union para representación de estados +- Serialización JSON integrada +- Extensiones CopyWith para actualizaciones + +### Almacenamiento Local: Hive + +- **hive_ce**: Edición comunitaria de Hive +- No se requiere `@HiveField` o `@HiveType` manual +- Adaptadores de tipo generados automáticamente +- Almacenamiento persistente clave-valor + +## Inyección de Dependencias + +Los servicios y almacenes se inyectan a través de: + +1. **Providers**: Exponen dependencias a la UI +2. **GetIt**: Localizador de servicios (donde sea aplicable) +3. **Inyección en Constructor**: Dependencias explícitas + +## Flujo de Datos + +``` +Acción de Usuario → Widget → Provider → Servicio/Almacén → Actualización de Modelo → Reconstrucción de UI +``` + +1. El usuario interactúa con el widget +2. El widget llama al método del provider +3. El provider actualiza el estado a través del servicio/almacén +4. El cambio de estado activa la reconstrucción de la UI +5. El nuevo estado se refleja en el 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 diff --git a/docs/src/content/docs/es/development/building.md b/docs/src/content/docs/es/development/building.md new file mode 100644 index 00000000..42db9d6a --- /dev/null +++ b/docs/src/content/docs/es/development/building.md @@ -0,0 +1,116 @@ +--- +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 +``` + +## Compilación de Producción + +El proyecto utiliza `fl_build` para compilar: + +```bash +# Compilar para una plataforma específica +dart run fl_build -p + +# Plataformas disponibles: +# - ios +# - android +# - macos +# - linux +# - windows +``` + +## Compilaciones Específicas por Plataforma + +### iOS + +```bash +dart run fl_build -p ios +``` + +Requiere: +- macOS con Xcode +- CocoaPods +- Cuenta de Apple Developer para la firma + +### Android + +```bash +dart run fl_build -p android +``` + +Requiere: +- Android SDK +- Java Development Kit +- Keystore para la firma + +### macOS + +```bash +dart run fl_build -p macos +``` + +### Linux + +```bash +dart run fl_build -p linux +``` + +### Windows + +```bash +dart run fl_build -p windows +``` + +Requiere Windows con Visual Studio. + +## Pre/Post Compilación + +El script `make.dart` se encarga de: + +- Generación de metadatos +- Actualización de cadenas de versión +- Configuraciones específicas de plataforma + +## Solución de Problemas + +### Compilación Limpia + +```bash +flutter clean +dart run build_runner build --delete-conflicting-outputs +flutter pub get +``` + +### Discrepancia de Versión + +Asegúrate de que todas las dependencias son compatibles: +```bash +flutter pub upgrade +``` + +## Lista de Verificación de Lanzamiento + +1. Actualizar la versión en `pubspec.yaml` +2. Ejecutar la generación de código +3. Ejecutar las pruebas +4. Compilar para todas las plataformas de destino +5. Probar en dispositivos físicos +6. Crear lanzamiento (release) en GitHub diff --git a/docs/src/content/docs/es/development/codegen.md b/docs/src/content/docs/es/development/codegen.md new file mode 100644 index 00000000..d1b13d3b --- /dev/null +++ b/docs/src/content/docs/es/development/codegen.md @@ -0,0 +1,98 @@ +--- +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 y regenerar +dart run build_runner build --delete-conflicting-outputs --clean +``` + +## 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; +} +``` + +### 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 json) => + _$ServerFromJson(json); + Map toJson() => _$ServerToJson(this); +} +``` + +### Providers de Riverpod (`*.g.dart`) + +Generados a partir de la anotación `@riverpod`: + +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; +} +``` + +### Adaptadores de Hive (`*.g.dart`) + +Auto-generados para modelos de Hive (hive_ce): + +```dart +@HiveType(typeId: 0) +class ServerModel { + @HiveField(0) + final String id; +} +``` + +## Generación de Localización + +```bash +flutter gen-l10n +``` + +Genera `lib/generated/l10n/` a partir de los archivos `lib/l10n/*.arb`. + +## Consejos + +- Usa `--delete-conflicting-outputs` para evitar conflictos +- Añade los archivos generados al `.gitignore` +- Nunca edites manualmente los archivos generados diff --git a/docs/src/content/docs/es/development/state.md b/docs/src/content/docs/es/development/state.md new file mode 100644 index 00000000..6610c854 --- /dev/null +++ b/docs/src/content/docs/es/development/state.md @@ -0,0 +1,115 @@ +--- +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; + } +} +``` + +### AsyncNotifierProvider + +Estado que se carga de forma asíncrona con estados de carga/error: + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + return fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => fetchStatus(server)); + } +} +``` + +### StreamProvider + +Datos en tiempo real desde flujos (streams): + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + return cpuService.monitor(server); +} +``` + +## Patrones de Estado + +### Estados de Carga + +```dart +state.when( + data: (data) => DataWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### Family Providers + +Providers parametrizados: + +```dart +@riverpod +List containers(ContainersRef ref, Server server) { + return containerService.list(server); +} +``` + +### Auto-Dispose + +Providers que se eliminan cuando ya no están referenciados: + +```dart +@Riverpod(keepAlive: false) +class TempState extends _$TempState { + // ... +} +``` + +## Mejores Prácticas + +1. **Usar generación de código**: Usa siempre la anotación `@riverpod` +2. **Co-localizar providers**: Ponlos cerca de los widgets que los consumen +3. **Evitar singletons**: Usa providers en su lugar +4. **Capas correctas**: Mantén la lógica de UI separada de la lógica de negocio + +## Leer el Estado en Widgets + +```dart +class ServerWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final status = ref.watch(serverStatusProvider(server)); + return status.when(...); + } +} +``` + +## Modificar el Estado + +```dart +ref.read(settingsProvider.notifier).update(newSettings); +``` diff --git a/docs/src/content/docs/es/development/structure.md b/docs/src/content/docs/es/development/structure.md new file mode 100644 index 00000000..a2cd9705 --- /dev/null +++ b/docs/src/content/docs/es/development/structure.md @@ -0,0 +1,96 @@ +--- +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 +``` + +## 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 diff --git a/docs/src/content/docs/es/development/testing.md b/docs/src/content/docs/es/development/testing.md new file mode 100644 index 00000000..fa51fd32 --- /dev/null +++ b/docs/src/content/docs/es/development/testing.md @@ -0,0 +1,113 @@ +--- +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 +``` + +## Estructura de las Pruebas + +Las pruebas se encuentran en el directorio `test/` reflejando la estructura de lib: + +``` +test/ +├── data/ +│ ├── model/ +│ └── provider/ +├── view/ +│ └── widget/ +└── test_helpers.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%'); +}); +``` + +## 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); +}); +``` + +## 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()); +}); +``` + +## Mocking (Simulaciones) + +Utilizar mocks para dependencias externas: + +```dart +class MockSshService extends Mock implements SshService {} + +test('se conecta al servidor', () async { + final mockSsh = MockSshService(); + when(mockSsh.connect(any)).thenAnswer((_) async => true); + + // Probar con el mock +}); +``` + +## Pruebas de Integración + +Probar flujos de usuario completos (en `integration_test/`): + +```dart +testWidgets('flujo de agregar servidor', (tester) async { + await tester.pumpWidget(MyApp()); + + // Tocar el botón de agregar + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + + // Completar el formulario + await tester.enterText(find.byKey(Key('name')), 'Test Server'); + // ... +}); +``` + +## 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. diff --git a/docs/src/content/docs/es/index.mdx b/docs/src/content/docs/es/index.mdx new file mode 100644 index 00000000..d602382a --- /dev/null +++ b/docs/src/content/docs/es/index.mdx @@ -0,0 +1,46 @@ +--- +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: /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 + + + + Monitorea CPU, memoria, disco, red, GPU y temperatura con hermosos gráficos en tiempo real. + + + Terminal SSH con todas las funciones, soporte para múltiples pestañas y teclado virtual para dispositivos móviles. + + + Administra archivos en tus servidores con el cliente SFTP integrado y el navegador de archivos local. + + + Inicia, detén y monitorea contenedores Docker con una interfaz intuitiva. + + + Disponible en iOS, Android, macOS, Linux, Windows y watchOS. + + + Soporte completo de localización que incluye inglés, chino, alemán, francés y más. + + + +## Enlaces Rápidos + +- **Descarga**: Disponible en [App Store](https://apps.apple.com/app/id1586449703), [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) y [F-Droid](https://f-droid.org/) +- **Documentación**: Explora las guías para comenzar con Server Box +- **Soporte**: Únete a nuestra comunidad en GitHub para discusiones y problemas diff --git a/docs/src/content/docs/es/installation.mdx b/docs/src/content/docs/es/installation.mdx new file mode 100644 index 00000000..0c740d4a --- /dev/null +++ b/docs/src/content/docs/es/installation.mdx @@ -0,0 +1,51 @@ +--- +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](https://apps.apple.com/app/id1586449703)**. + +### Android + +Elige tu fuente preferida: + +- **[F-Droid](https://f-droid.org/)** - Para usuarios que prefieren fuentes exclusivamente FOSS (Software Libre y de Código Abierto) +- **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** - Para la última versión directamente desde la fuente + +## Aplicaciones de Escritorio + +### macOS + +Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +Características: +- Integración nativa con la barra de menú +- Soporte para Intel y Apple Silicon + +### Linux + +Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +Disponible en paquetes AppImage, deb o tar.gz. + +### Windows + +Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +## watchOS + +Disponible en la **[App Store](https://apps.apple.com/app/id1586449703)** 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](/es/development/building/) en la documentación de desarrollo. + +## Información de Versión + +Consulta la página de [GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases) para ver la última versión y el registro de cambios. diff --git a/docs/src/content/docs/es/introduction.mdx b/docs/src/content/docs/es/introduction.mdx new file mode 100644 index 00000000..f5305774 --- /dev/null +++ b/docs/src/content/docs/es/introduction.mdx @@ -0,0 +1,32 @@ +--- +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](https://github.com/lollipopkit/flutter_server_box). diff --git a/docs/src/content/docs/es/platforms/desktop.md b/docs/src/content/docs/es/platforms/desktop.md new file mode 100644 index 00000000..bf75c352 --- /dev/null +++ b/docs/src/content/docs/es/platforms/desktop.md @@ -0,0 +1,80 @@ +--- +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 diff --git a/docs/src/content/docs/es/platforms/mobile.md b/docs/src/content/docs/es/platforms/mobile.md new file mode 100644 index 00000000..079ee6c8 --- /dev/null +++ b/docs/src/content/docs/es/platforms/mobile.md @@ -0,0 +1,77 @@ +--- +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 diff --git a/docs/src/content/docs/es/principles/architecture.md b/docs/src/content/docs/es/principles/architecture.md new file mode 100644 index 00000000..2f82f1cc --- /dev/null +++ b/docs/src/content/docs/es/principles/architecture.md @@ -0,0 +1,214 @@ +--- +title: Descripción General de la Arquitectura +description: Arquitectura de alto nivel de la aplicación +--- + +Server Box sigue una arquitectura por capas con una clara separación de responsabilidades. + +## Capas de la Arquitectura + +``` +┌─────────────────────────────────────────────────┐ +│ Capa de Presentación (UI) │ +│ lib/view/page/, lib/view/widget/ │ +│ - Páginas, Widgets, Controladores │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Capa de Lógica de Negocio │ +│ lib/data/provider/ │ +│ - Riverpod Providers, State Notifiers │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Capa de Acceso a Datos │ +│ lib/data/store/, lib/data/model/ │ +│ - Hive Stores, Modelos de Datos │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Capa de Integración Externa │ +│ - SSH (dartssh2), Terminal (xterm), SFTP │ +│ - Código específico de plataforma (iOS, etc.) │ +└─────────────────────────────────────────────────┘ +``` + +## Fundamentos de la Aplicación + +### Punto de Entrada Principal + +`lib/main.dart` inicializa la aplicación: + +```dart +void main() { + runApp( + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +### Widget Raíz + +`MyApp` proporciona: +- **Gestión de Temas**: Cambio entre tema claro/oscuro +- **Configuración de Rutas**: Estructura de navegación +- **Provider Scope**: Raíz para la inyección de dependencias + +### Página de Inicio + +`HomePage` sirve como núcleo de navegación: +- **Interfaz de Pestañas**: Servidor, Snippet, Contenedor, SSH +- **Gestión de Estado**: Estado por pestaña +- **Navegación**: Acceso a funciones + +## Sistemas Principales + +### Gestión de Estado: Riverpod + +**¿Por qué Riverpod?** +- Seguridad en tiempo de compilación +- Facilidad para realizar pruebas +- Sin dependencia del Build context +- Funciona en todas las plataformas + +**Tipos de Provider Utilizados:** +- `StateProvider`: Estado mutable simple +- `AsyncNotifierProvider`: Estados de carga/error/datos +- `StreamProvider`: Flujos de datos en tiempo real +- Future providers: Operaciones asíncronas únicas + +### Persistencia de Datos: Hive CE + +**¿Por qué Hive CE?** +- Sin dependencias de código nativo +- Almacenamiento clave-valor rápido +- Tipado seguro con generación de código +- Sin necesidad de anotaciones manuales de campos + +**Almacenes (Stores):** +- `SettingStore`: Preferencias de la app +- `ServerStore`: Configuraciones de servidores +- `SnippetStore`: Fragmentos de comandos +- `KeyStore`: Claves SSH + +### Modelos Inmutables: Freezed + +**Beneficios:** +- Inmutabilidad en tiempo de compilación +- Tipos Union para el estado +- Serialización JSON integrada +- Extensiones CopyWith + +## Estrategia Multiplataforma + +### Sistema de Plugins + +Los plugins de Flutter proporcionan la integración con la plataforma: + +| Plataforma | Método de Integración | +|------------|-----------------------| +| iOS | CocoaPods, Swift/Obj-C | +| Android | Gradle, Kotlin/Java | +| macOS | CocoaPods, Swift | +| Linux | CMake, C++ | +| Windows | CMake, C# | + +### Funciones Específicas por Plataforma + +**Solo iOS:** +- Widgets de pantalla de inicio +- Actividades en Directo (Live Activities) +- Compañero de Apple Watch + +**Solo Android:** +- Servicio en segundo plano +- Notificaciones push +- Acceso al sistema de archivos + +**Solo Escritorio:** +- Integración en la barra de menús +- Múltiples ventanas +- Barra de título personalizada + +## Dependencias Personalizadas + +### Rama (Fork) de dartssh2 + +Cliente SSH mejorado con: +- Mejor soporte para móviles +- Gestión de errores mejorada +- Optimizaciones de rendimiento + +### Rama (Fork) de xterm.dart + +Emulador de terminal con: +- Renderizado optimizado para móviles +- Soporte para gestos táctiles +- Integración con teclado virtual + +### fl_lib + +Paquete de utilidades compartidas con: +- Widgets comunes +- Extensiones +- Funciones de ayuda + +## Sistema de Compilación + +### Paquete fl_build + +Sistema de compilación personalizado para: +- Compilaciones multiplataforma +- Firma de código +- Empaquetado de recursos (assets) +- Gestión de versiones + +### Proceso de Compilación + +``` +make.dart (versión) → fl_build (compilación) → Salida de plataforma +``` + +1. **Pre-compilación**: Cálculo de la versión desde Git +2. **Compilación**: Compilar para la plataforma de destino +3. **Post-compilación**: Empaquetado y 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 +``` + +### Flujo de Acción del Usuario + +``` +1. El usuario toca un botón → +2. El Widget llama al método del provider → +3. El Provider actualiza el estado → +4. El cambio de estado activa la reconstrucción → +5. El nuevo estado se refleja en la UI +``` + +## Arquitectura de Seguridad + +### Protección de Datos + +- **Contraseñas**: Cifradas con flutter_secure_storage +- **Claves SSH**: Cifradas en reposo +- **Huellas de Host**: Almacenadas de forma segura +- **Datos de Sesión**: No se persisten + +### Seguridad de Conexión + +- **Verificación de Clave de Host**: Detección de MITM +- **Cifrado**: Cifrado SSH estándar +- **Sin Texto Plano**: Los datos sensibles nunca se almacenan en plano diff --git a/docs/src/content/docs/es/principles/sftp.md b/docs/src/content/docs/es/principles/sftp.md new file mode 100644 index 00000000..2e51fb87 --- /dev/null +++ b/docs/src/content/docs/es/principles/sftp.md @@ -0,0 +1,490 @@ +--- +title: Sistema SFTP +description: Cómo funciona el explorador de archivos SFTP +--- + +El sistema SFTP proporciona capacidades de gestión de archivos sobre SSH. + +## Arquitectura + +``` +┌─────────────────────────────────────────────┐ +│ Capa UI de SFTP │ +│ - Explorador de archivos (remoto) │ +│ - Explorador de archivos (local) │ +│ - Cola de transferencia │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Gestión de Estado SFTP │ +│ - sftpProvider │ +│ - Gestión de rutas │ +│ - Cola de operaciones │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Capa de Protocolo SFTP │ +│ - Subsistema SSH │ +│ - Operaciones de archivos │ +│ - Listado de directorios │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Transporte SSH │ +│ - Canal seguro │ +│ - Streaming de datos │ +└─────────────────────────────────────────────┘ +``` + +## Establecimiento de la Conexión + +### Creación del Cliente SFTP + +```dart +Future createSftpClient(Spi spi) async { + // 1. Obtener cliente SSH (reutilizar si está disponible) + final sshClient = await genClient(spi); + + // 2. Abrir subsistema SFTP + final sftp = await sshClient.openSftp(); + + return sftp; +} +``` + +### Reutilización de Conexiones + +SFTP reutiliza las conexiones SSH existentes: + +```dart +class ServerProvider { + SSHClient? _sshClient; + SftpClient? _sftpClient; + + Future getSftpClient(String spiId) async { + _sftpClient ??= await _sshClient!.openSftp(); + return _sftpClient!; + } +} +``` + +## Operaciones del Sistema de Archivos + +### Listado de Directorios + +```dart +Future> listDirectory(String path) async { + final sftp = await getSftpClient(spiId); + + // Listar directorio + final files = await sftp.listDir(path); + + // Ordenar según ajustes + files.sort((a, b) { + switch (sortOption) { + case SortOption.name: + return a.name.toLowerCase().compareTo(b.name.toLowerCase()); + case SortOption.size: + return a.size.compareTo(b.size); + case SortOption.time: + return a.modified.compareTo(b.modified); + } + }); + + // Carpetas primero si está activado + if (showFoldersFirst) { + final dirs = files.where((f) => f.isDirectory); + final regular = files.where((f) => !f.isDirectory); + return [...dirs, ...regular]; + } + + return files; +} +``` + +### Metadatos de Archivo + +```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); +} +``` + +## Operaciones de Archivo + +### Subida (Upload) + +```dart +Future uploadFile( + String localPath, + String remotePath, +) async { + final sftp = await getSftpClient(spiId); + + // Crear petición + final req = SftpReq( + spi: spi, + remotePath: remotePath, + localPath: localPath, + type: SftpReqType.upload, + ); + + // Añadir a la cola + _transferQueue.add(req); + + // Ejecutar transferencia con progreso + final file = File(localPath); + final size = await file.length(); + final stream = file.openRead(); + + await sftp.upload( + stream: stream, + toPath: remotePath, + onProgress: (transferred) { + _updateProgress(req, transferred, size); + }, + ); + + // Completar + _transferQueue.remove(req); +} +``` + +### Descarga (Download) + +```dart +Future downloadFile( + String remotePath, + String localPath, +) async { + final sftp = await getSftpClient(spiId); + + // Crear archivo local + final file = File(localPath); + final sink = file.openWrite(); + + // Descargar con progreso + final stat = await sftp.stat(remotePath); + + await sftp.download( + fromPath: remotePath, + toSink: sink, + onProgress: (transferred) { + _updateProgress( + SftpReq(...), + transferred, + stat.size, + ); + }, + ); + + await sink.close(); +} +``` + +### Edición de Permisos + +```dart +Future setPermissions( + String path, + String permissions, +) async { + final sftp = await getSftpClient(spiId); + + // Analizar permisos (ej., "rwxr-xr-x" o "755") + final mode = parsePermissions(permissions); + + // Establecer vía comando SSH (más fiable que SFTP) + final ssh = await getSshClient(spiId); + await ssh.exec('chmod $mode "$path"'); +} +``` + +## Gestión de Rutas + +### Estructura de Rutas + +```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, + ); + } +} +``` + +### Historial de Navegación + +```dart +class PathHistory { + final List _history = []; + int _index = -1; + + void push(String path) { + // Eliminar historial hacia adelante + _history.removeRange(_index + 1, _history.length); + _history.add(path); + _index = _history.length - 1; + } + + String? back() { + if (_index > 0) { + _index--; + return _history[_index]; + } + return null; + } + + String? forward() { + if (_index < _history.length - 1) { + _index++; + return _history[_index]; + } + return null; + } +} +``` + +## Sistema de Transferencia + +### Petición de Transferencia + +```dart +class SftpReq { + final Spi spi; + final String remotePath; + final String localPath; + final SftpReqType type; + final DateTime createdAt; + + int? totalBytes; + int? transferredBytes; + String? error; +} +``` + +### Seguimiento de Progreso + +```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); + } +} +``` + +### Gestión de Colas + +```dart +class TransferQueue { + final List _queue = []; + final Map _progress = {}; + int _concurrent = 3; // Transferencias concurrentes máx. + + Future 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 _executeTransfer(SftpReq req) async { + try { + _progress[req.id] = TransferProgress.inProgress(req); + + if (req.type == SftpReqType.upload) { + await uploadFile(req.localPath, req.remotePath); + } else { + await downloadFile(req.remotePath, req.localPath); + } + + _progress[req.id] = TransferProgress.completed(req); + } catch (e) { + _progress[req.id] = TransferProgress.failed(req, e); + } + } +} +``` + +## Patrón de Almacenamiento Local + +### Caché de Descargas + +Los archivos descargados se guardan en: + +```dart +String getLocalDownloadPath(String spiId, String remotePath) { + final normalized = remotePath.replaceAll('/', '_'); + return 'Paths.file/$spiId/$normalized'; +} +``` + +Ejemplo: +- Remoto: `/var/log/nginx/access.log` +- spiId: `server-123` +- Local: `Paths.file/server-123/_var_log_nginx_access.log` + +## Edición de Archivos + +### Flujo de Trabajo de Edición + +```dart +Future editFile(String path) async { + final sftp = await getSftpClient(spiId); + + // 1. Comprobar tamaño + final stat = await sftp.stat(path); + if (stat.size > editorMaxSize) { + showWarning('Archivo demasiado grande para el editor integrado'); + return; + } + + // 2. Descargar a temporal + final temp = await downloadToTemp(path); + + // 3. Abrir en editor + final content = await openEditor(temp.path); + + // 4. Subir de nuevo + await uploadFile(temp.path, path); + + // 5. Limpieza + await temp.delete(); +} +``` + +### Integración con Editor Externo + +```dart +Future editInExternalEditor(String path) async { + final ssh = await getSshClient(spiId); + + // Abrir terminal con editor + final editor = getSetting('sftpEditor', 'vim'); + await ssh.exec('$editor "$path"'); + + // El usuario edita en la terminal + // Tras guardar, refrescar la vista SFTP +} +``` + +## Gestión de Errores + +### Errores de Permiso + +```dart +try { + await sftp.upload(...); +} on SftpPermissionException { + showError('Permiso denegado: ${stat.path}'); + showHint('Comprueba los permisos y la propiedad del archivo'); +} +``` + +### Erreores de Conexión + +```dart +try { + await sftp.listDir(path); +} on SftpConnectionException { + showError('Conexión perdida'); + await reconnect(); +} +``` + +### Errores de Espacio + +```dart +try { + await sftp.upload(...); +} on SftpNoSpaceException { + showError('Disco lleno en el servidor remoto'); +} +``` + +## Optimizaciones de Rendimiento + +### Caché de Directorios + +```dart +class DirectoryCache { + final Map _cache = {}; + final Duration ttl = Duration(minutes: 5); + + Future> list(String path) async { + final cached = _cache[path]; + if (cached != null && !cached.isExpired) { + return cached.files; + } + + final files = await sftp.listDir(path); + _cache[path] = CachedDirectory(files); + return files; + } +} +``` + +### Carga Perezosa (Lazy Loading) + +Para directorios grandes (>1000 elementos): + +```dart +List loadPage(String path, int page, int pageSize) { + final all = cache[path] ?? []; + final start = page * pageSize; + final end = start + pageSize; + return all.sublist(start, end.clamp(0, all.length)); +} +``` + +### Paginación + +```dart +class PaginatedDirectory { + static const pageSize = 100; + + Future> getPage(int page) async { + final offset = page * pageSize; + return await sftp.listDir( + path, + offset: offset, + limit: pageSize, + ); + } +} +``` diff --git a/docs/src/content/docs/es/principles/ssh.md b/docs/src/content/docs/es/principles/ssh.md new file mode 100644 index 00000000..03f54a35 --- /dev/null +++ b/docs/src/content/docs/es/principles/ssh.md @@ -0,0 +1,305 @@ +--- +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 +``` + +### 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 +} +``` + +### Paso 2: Generación del Cliente + +`genClient(spi)` crea el cliente SSH: + +```dart +Future genClient(Spi spi) async { + // 1. Establecer socket + var socket = await connect(spi.ip, spi.port); + + // 2. Probar URL alternativa si falla + if (socket == null && spi.alterUrl != null) { + socket = await connect(spi.alterUrl, spi.port); + } + + if (socket == null) { + throw ConnectionException('Unable to connect'); + } + + // 3. Autenticar + final client = SSHClient( + socket: socket, + username: spi.user, + onPasswordRequest: () => spi.pwd, + onIdentityRequest: () => loadKey(spi.keyId), + ); + + // 4. Verificar clave de host + await verifyHostKey(client, spi); + + return client; +} +``` + +### Paso 3: Servidor de Salto (si está configurado) + +Para servidores de salto, conexión recursiva: + +```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 +} +``` + +## Métodos de Autenticación + +### Autenticación por Contraseña + +```dart +onPasswordRequest: () => spi.pwd +``` + +- 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); +} +``` + +**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; +} +``` + +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} +``` + +Ejemplo: +```text +mi-servidor::ssh-ed25519 +mi-servidor::ecdsa-sha2-nistp256 +``` + +### Formatos de Huella Digital (Fingerprint) + +**MD5 Hex:** +```text +aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99 +``` + +**Base64:** +```text +SHA256:AbCdEf1234567890...= +``` + +### Flujo de Verificación + +```dart +Future verifyHostKey(SSHClient client, Spi spi) async { + final key = await client.hostKey; + final keyType = key.type; + final fingerprint = md5Hex(key); // o base64 + + final stored = SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType']; + + if (stored == null) { + // Nuevo host - preguntar al usuario + final trust = await promptUser( + 'Host desconocido', + 'Huella: $fingerprint', + ); + if (trust) { + SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType'] = fingerprint; + } + } else if (stored != fingerprint) { + // Ha cambiado - advertir al usuario + await warnUser( + '¡La clave de host ha cambiado!', + 'Posible ataque MITM', + ); + } +} +``` + +## Gestión de Sesiones + +### Pool de Conexiones + +Clientes activos mantenidos en `ServerProvider`: + +```dart +class ServerProvider { + final Map _clients = {}; + + SSHClient getClient(String spiId) { + return _clients[spiId] ??= connect(spiId); + } +} +``` + +### Keep-Alive + +Mantener la conexión durante la inactividad: + +```dart +Timer.periodic( + Duration(seconds: 30), + (_) => client.sendKeepAlive(), +); +``` + +### Reconexión Automática + +Al perder la conexión: + +```dart +client.onError.listen((error) async { + await Future.delayed(Duration(seconds: 5)); + reconnect(); +}); +``` + +## Ciclo de Vida de la Conexión + +```text +┌─────────────┐ +│ Inicial │ +└──────┬──────┘ + │ connect() + ↓ +┌─────────────┐ +│ Conectando │ ←──┐ +└──────┬──────┘ │ + │ éxito │ + ↓ │ fallo (reintento) +┌─────────────┐ │ +│ Conectado │───┘ +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ Activo │ ──→ Enviar comandos +└──────┬──────┘ + │ + ↓ (error/desconexión) +┌─────────────┐ +│ Desconectado│ +└─────────────┘ +``` + +## 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'); +} +``` + +### 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'; +} +``` + +### Discrepancia en Clave de Host + +```dart +onHostKeyMismatch: (stored, current) { + showSecurityWarning( + '¡La clave de host ha cambiado!', + 'Posible ataque MITM', + ); +} +``` + +## Consideraciones de Rendimiento + +### Reutilización de Conexiones + +- Reutilizar clientes entre funciones +- No desconectar/reconectar innecesariamente +- Pool de conexiones para operaciones concurrentes + +### Ajustes Óptimos + +- **Timeout**: 30 segundos (ajustable) +- **Keep-alive**: Cada 30 segundos +- **Retraso de reintento**: 5 segundos + +### Eficiencia de Red + +- Conexión única para múltiples operaciones +- Comandos en tubería (pipeline) cuando sea posible +- Evitar abrir múltiples conexiones diff --git a/docs/src/content/docs/es/principles/state.md b/docs/src/content/docs/es/principles/state.md new file mode 100644 index 00000000..4f569d5d --- /dev/null +++ b/docs/src/content/docs/es/principles/state.md @@ -0,0 +1,167 @@ +--- +title: Gestión de Estado +description: Cómo se gestiona el estado con Riverpod +--- + +Entendiendo la arquitectura de gestión de estado en Server Box. + +## ¿Por qué Riverpod? + +**Beneficios Clave:** +- **Seguridad en tiempo de compilación**: Detecta errores al compilar +- **Sin necesidad de BuildContext**: Accede al estado desde cualquier lugar +- **Facilidad de pruebas**: Sencillo de probar providers de forma aislada +- **Generación de código**: Menos código repetitivo, tipado seguro + +## Arquitectura de Providers + +``` +┌─────────────────────────────────────────────┐ +│ Capa UI (Widgets) │ +│ - ConsumerWidget / ConsumerStatefulWidget │ +│ - ref.watch() / ref.read() │ +└─────────────────────────────────────────────┘ + ↓ observa (watches) +┌─────────────────────────────────────────────┐ +│ Capa de Provider │ +│ - Anotaciones @riverpod │ +│ - Archivos *.g.dart generados │ +└─────────────────────────────────────────────┘ + ↓ usa (uses) +┌─────────────────────────────────────────────┐ +│ Capa de Servicio / Store │ +│ - Lógica de negocio │ +│ - Acceso a datos │ +└─────────────────────────────────────────────┘ +``` + +## Tipos de Provider Utilizados + +### 1. StateProvider (Estado Simple) + +Para estados simples y observables: + +```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 + } +} +``` + +**Uso:** +```dart +class MyWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themeNotifierProvider); + return Text('Tema: $theme'); + } +} +``` + +### 2. AsyncNotifierProvider (Estado Asíncrono) + +Para datos que se cargan de forma asíncrona: + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + // Carga inicial + return await fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + return await fetchStatus(server); + }); + } +} +``` + +**Uso:** +```dart +final status = ref.watch(serverStatusProvider(server)); + +status.when( + data: (data) => StatusWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 3. StreamProvider (Datos en Tiempo Real) + +Para flujos de datos continuos: + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + final client = ref.watch(sshClientProvider(server)); + final stream = client.monitorCpu(); + + // Liberación automática cuando no se observa + ref.onDispose(() { + client.stopMonitoring(); + }); + + return stream; +} +``` + +**Uso:** +```dart +final cpu = ref.watch(cpuUsageProvider(server)); + +cpu.when( + data: (usage) => CpuChart(usage), + loading: () => CircularProgressIndicator(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 4. Family Providers (Parametrizados) + +Providers que aceptan parámetros: + +```dart +@riverpod +Future> containers(ContainersRef ref, Server server) async { + final client = await ref.watch(sshClientProvider(server).future); + return await client.listContainers(); +} +``` + +**Uso:** +```dart +final containers = ref.watch(containersProvider(server)); + +// Diferentes servidores = diferentes estados en caché +final containers2 = ref.watch(containersProvider(server2)); +``` + +## Optimizaciones de Rendimiento + +- **Provider Keep-Alive**: Usa `@Riverpod(keepAlive: true)` para evitar que se destruya automáticamente cuando no haya escuchadores. +- **Observación selectiva**: Usa `select` para observar solo una parte específica del estado. +- **Caché de Providers**: Los Family providers cachean resultados por parámetro. + +## Mejores Prácticas + +1. **Co-localizar providers**: Colócalos cerca de los widgets que los consumen. +2. **Usar generación de código**: Usa siempre `@riverpod`. +3. **Mantener providers enfocados**: Responsabilidad única. +4. **Gestionar estados de carga**: Maneja siempre los estados de AsyncValue. +5. **Liberar recursos**: Usa `ref.onDispose()` para la limpieza. +6. **Evitar árboles de providers profundos**: Mantén el grafo de providers plano. diff --git a/docs/src/content/docs/es/principles/terminal.md b/docs/src/content/docs/es/principles/terminal.md new file mode 100644 index 00000000..d54a5757 --- /dev/null +++ b/docs/src/content/docs/es/principles/terminal.md @@ -0,0 +1,198 @@ +--- +title: Implementación de la Terminal +description: Cómo funciona internamente la terminal SSH +--- + +La terminal SSH es una de las funciones más complejas, construida sobre un fork personalizado de xterm.dart. + +## Resumen de la Arquitectura + +``` +┌─────────────────────────────────────────────┐ +│ Capa de UI de la Terminal │ +│ - Gestión de pestañas │ +│ - Teclado virtual │ +│ - Selección de texto │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Emulador xterm.dart │ +│ - PTY (Pseudo Terminal) │ +│ - Emulación VT100/ANSI │ +│ - Motor de renderizado │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Capa de Cliente SSH │ +│ - Sesión SSH │ +│ - Gestión de canales │ +│ - Streaming de datos │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Servidor Remoto │ +│ - Proceso de Shell │ +│ - Ejecución de comandos │ +└─────────────────────────────────────────────┘ +``` + +## Ciclo de Vida de la Sesión de Terminal + +### 1. Creación de la Sesión + +```dart +Future createSession(Spi spi) async { + // 1. Obtener cliente SSH + final client = await genClient(spi); + + // 2. Crear PTY + final pty = await client.openPty( + term: 'xterm-256color', + cols: 80, + rows: 24, + ); + + // 3. Inicializar emulador de terminal + final terminal = Terminal( + backend: PtyBackend(pty), + ); + + // 4. Configurar manejador de cambio de tamaño + terminal.onResize.listen((size) { + pty.resize(size.cols, size.rows); + }); + + return TerminalSession( + terminal: terminal, + pty: pty, + client: client, + ); +} +``` + +### 2. Emulación de Terminal + +El fork de xterm.dart proporciona: + +**Emulación VT100/ANSI:** +- Movimiento del cursor +- Colores (soporte para 256 colores) +- Atributos de texto (negrita, subrayado, etc.) +- Regiones de desplazamiento +- Búfer de pantalla alternativo + +**Renderizado:** +- Renderizado basado en líneas +- Soporte para texto bidireccional +- Soporte para Unicode/emoji +- Redibujado optimizado + +### 3. Flujo de Datos + +``` +Entrada del Usuario + ↓ +Teclado Virtual / Teclado Físico + ↓ +Emulador de Terminal (tecla → secuencia de escape) + ↓ +Canal SSH (envío) + ↓ +PTY Remoto + ↓ +Shell Remoto + ↓ +Salida del Comando + ↓ +Canal SSH (recepción) + ↓ +Emulador de Terminal (analizar códigos ANSI) + ↓ +Renderizado en Pantalla +``` + +## Sistema de Múltiples Pestañas + +### Gestión de Pestañas + +Las pestañas mantienen su estado durante la navegación: +- La conexión SSH se mantiene activa +- Se preserva el estado de la terminal +- Se mantiene el búfer de desplazamiento +- Se retiene el historial de entrada + +## Teclado Virtual + +### Implementación Específica por Plataforma + +**iOS:** +- Teclado personalizado basado en UIView +- Conmutable con un botón de teclado +- Mostrar/ocultar automáticamente basado en el enfoque + +**Android:** +- Método de entrada personalizado +- Integrado con el teclado del sistema +- Botones de acción rápida + +### Botones del Teclado + +| Botón | Acción | +|--------|--------| +| **Conmutar** | Mostrar/ocultar teclado del sistema | +| **Ctrl** | Enviar modificador Ctrl | +| **Alt** | Enviar modificador Alt | +| **SFTP** | Abrir directorio actual | +| **Portapapeles** | Copiar/Pegar sensible al contexto | +| **Snippets** | Ejecutar fragmento de código | + +## Selección de Texto + +1. **Pulsación larga**: Entrar en modo selección +2. **Arrastrar**: Extender la selección +3. **Soltar**: Copiar al portapapeles + +## Fuente y Dimensiones + +### Cálculo de Tamaño + +```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()); + } +} +``` + +### Pellizcar para Ampliar (Pinch-to-Zoom) + +```dart +GestureDetector( + onScaleStart: () => _baseFontSize = currentFontSize, + onScaleUpdate: (details) { + final newFontSize = _baseFontSize * details.scale; + resize(newFontSize); + }, +) +``` + +## Esquema de Colores + +- **Claro (Light)**: Fondo claro, texto oscuro +- **Oscuro (Dark)**: Fondo oscuro, texto claro +- **AMOLED**: Fondo negro puro + +## Optimizaciones de Rendimiento + +- **Dirty rectangle**: Solo redibujar las regiones cambiadas +- **Caché de líneas**: Cachear las líneas renderizadas +- **Desplazamiento perezoso (Lazy scrolling)**: Desplazamiento virtual para búferes largos +- **Actualizaciones por lotes**: Unificar múltiples escrituras +- **Compresión**: Comprimir el búfer de desplazamiento +- **Debouncing**: Antirrebote para entradas rápidas diff --git a/docs/src/content/docs/es/quick-start.mdx b/docs/src/content/docs/es/quick-start.mdx new file mode 100644 index 00000000..d659b711 --- /dev/null +++ b/docs/src/content/docs/es/quick-start.mdx @@ -0,0 +1,45 @@ +--- +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) diff --git a/docs/src/content/docs/features/docker.md b/docs/src/content/docs/features/docker.md deleted file mode 100644 index a94c6b3a..00000000 --- a/docs/src/content/docs/features/docker.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: Docker Management -description: Monitor and manage Docker containers ---- - -Flutter Server Box provides an intuitive interface for managing Docker containers on your servers. - -## Features - -### Container List - -- View all containers (running and stopped) -- Container ID and name display -- Image information -- Status indicators -- Creation time - -### Container Actions - -- **Start**: Launch stopped containers -- **Stop**: Gracefully stop running containers -- **Restart**: Restart containers -- **Remove**: Delete containers -- **View Logs**: Check container logs -- **Inspect**: View container details - -### Container Details - -- Environment variables -- Port mappings -- Volume mounts -- Network configuration -- Resource usage - -## Requirements - -- Docker must be installed on your server -- SSH user must have Docker permissions -- For non-root users, add to docker group: - ```bash - sudo usermod -aG docker your_username - ``` - -## Quick Actions - -- Single tap: View container details -- Long press: Quick action menu -- Swipe: Quick start/stop -- Bulk select: Multiple container operations - -## Tips - -- Use **auto-refresh** to monitor container status changes -- Filter by running/stopped containers -- Search containers by name or ID diff --git a/docs/src/content/docs/features/monitoring.md b/docs/src/content/docs/features/monitoring.md deleted file mode 100644 index fc60df56..00000000 --- a/docs/src/content/docs/features/monitoring.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Server Monitoring -description: Real-time server status monitoring with beautiful charts ---- - -Flutter Server Box provides comprehensive real-time monitoring of your server's health and performance. - -## Status Cards - -The server detail page displays configurable status cards for different system metrics. You can enable/disable cards in settings. - -### CPU Monitoring - -- Real-time CPU usage percentage -- Per-core CPU usage breakdown -- Historical usage charts -- CPU frequency information - -### Memory Monitoring - -- **RAM Usage**: Used vs total memory with percentage -- **Swap Usage**: Swap memory utilization -- Memory pressure indicators -- Historical memory trends - -### Disk Monitoring - -- Mount point usage with percentage -- Total, used, and free space -- I/O statistics -- Multiple disk support - -### Network Monitoring - -- Real-time upload/download speeds -- Bandwidth usage charts -- Network interface statistics -- Total data transferred - -### Advanced Metrics - -- **GPU Status**: NVIDIA and AMD GPU monitoring -- **Temperature**: CPU, GPU, and system temperatures -- **Sensors**: Fan speeds, voltages, and other sensor data -- **S.M.A.R.T**: Disk health monitoring -- **Battery**: UPS or battery status (if available) - -## Customizing Display - -### Reordering Cards - -1. Go to Settings -2. Select Server Settings -3. Drag cards to reorder them on the server detail page - -### Enabling/Disabling Cards - -1. Open a server's detail page -2. Tap the edit/menu button -3. Toggle individual cards on or off - -## Auto-Refresh - -- Status cards automatically refresh -- Refresh interval is configurable in settings -- Manual refresh available with pull-to-refresh gesture - -## Charts and Visualizations - -- **Line Charts**: Historical data trends -- **Gauge Charts**: Current usage percentage -- **Color Coding**: Visual indicators for status levels -- **Zoom**: Pinch to zoom on charts for detailed views diff --git a/docs/src/content/docs/features/network.md b/docs/src/content/docs/features/network.md deleted file mode 100644 index ee023f4e..00000000 --- a/docs/src/content/docs/features/network.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -title: Network Tools -description: Network testing and diagnostic tools ---- - -Flutter Server Box includes several network tools for testing and diagnostics. - -## iPerf - -Perform network speed tests between your device and server. - -### Features - -- **Upload/Download Speed**: Test bandwidth -- **Server Mode**: Use server as iPerf server -- **Client Mode**: Connect to iPerf servers -- **Custom Parameters**: Duration, parallel streams, etc. - -### Usage - -1. Open a server -2. Tap **iPerf** -3. Choose server or client mode -4. Configure parameters -5. Start test - -## Ping - -Test network connectivity and latency. - -### Features - -- **ICMP Ping**: Standard ping tool -- **Packet Count**: Specify number of packets -- **Packet Size**: Custom packet size -- **Interval**: Time between pings - -### Usage - -1. Open a server -2. Tap **Ping** -3. Enter target host -4. Configure parameters -5. Start pinging - -## Wake on LAN - -Wake up remote servers via magic packet. - -### Features - -- **MAC Address**: Target device MAC -- **Broadcast**: Send broadcast magic packet -- **Saved Profiles**: Store WoL configurations - -### Requirements - -- Target device must support Wake-on-LAN -- WoL must be enabled in BIOS/UEFI -- Device must be in sleep/soft-off state -- Device must be on the same network or reachable via broadcast - -## Tips - -- Use iPerf to diagnose network bottlenecks -- Ping multiple hosts to compare latency -- Save WoL profiles for frequently woken devices diff --git a/docs/src/content/docs/features/process.md b/docs/src/content/docs/features/process.md deleted file mode 100644 index 99334f59..00000000 --- a/docs/src/content/docs/features/process.md +++ /dev/null @@ -1,56 +0,0 @@ ---- -title: Process & Services -description: Monitor processes and manage systemd services ---- - -## Process Management - -View and manage running processes on your servers. - -### Process List - -- All running processes with details -- PID (Process ID) -- CPU and memory usage -- User ownership -- Process command - -### Process Actions - -- **Kill**: Terminate processes -- **Filter**: By name or user -- **Sort**: By CPU, memory, or PID -- **Search**: Find specific processes - -## Systemd Services - -Manage systemd services for service control. - -### Service List - -- All systemd services -- Active/inactive status -- Enabled/disabled state -- Service description - -### Service Actions - -- **Start**: Launch a stopped service -- **Stop**: Stop a running service -- **Restart**: Restart a service -- **Enable**: Enable auto-start on boot -- **Disable**: Disable auto-start -- **View Status**: Check service status and logs -- **Reload**: Reload service configuration - -## Requirements - -- SSH user must have appropriate permissions -- For service management: `sudo` access may be required -- Process viewing: Standard user permissions usually sufficient - -## Tips - -- Use process list to identify resource hogs -- Check service logs for troubleshooting -- Monitor critical services with auto-refresh diff --git a/docs/src/content/docs/features/pve.md b/docs/src/content/docs/features/pve.md deleted file mode 100644 index f3672e71..00000000 --- a/docs/src/content/docs/features/pve.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: Proxmox (PVE) -description: Proxmox Virtual Environment management ---- - -Flutter Server Box includes support for managing Proxmox VE virtualization platform. - -## Features - -### VM Management - -- **List VMs**: View all virtual machines -- **VM Status**: Check running/stopped states -- **VM Actions**: Start, stop, restart VMs -- **VM Details**: View configuration and resources - -### Container (LXC) Management - -- **List Containers**: View all LXC containers -- **Container Status**: Monitor container states -- **Container Actions**: Start, stop, restart containers -- **Console Access**: Terminal access to containers - -### Node Monitoring - -- **Resource Usage**: CPU, memory, disk, network -- **Node Status**: Check node health -- **Cluster View**: Multi-node cluster overview - -## Setup - -### Adding PVE Server - -1. Add server as normal SSH connection -2. Ensure user has PVE permissions -3. Access PVE features from server detail page - -### Permissions Required - -PVE user needs: - -- **VM.Audit**: View VM status -- **VM.PowerMgmt**: Start/stop VMs -- **VM.Console**: Console access - -Example permissions setup: - -```bash -pveum useradd myuser -password mypass -pveum aclmod /vms -user myuser@pve -role VMAdmin -``` - -## Usage - -### VM Management - -1. Open server with PVE -2. Tap **PVE** button -3. View VM list -4. Tap VM for details -5. Use action buttons for management - -### Container Management - -1. Open server with PVE -2. Tap **PVE** button -3. Switch to Containers tab -4. View and manage LXC containers - -### Monitoring - -- Real-time resource usage -- Historical data charts -- Multiple node support - -## Features by Status - -### Implemented - -- VM listing and status -- Container listing and status -- Basic VM operations (start/stop/restart) -- Resource monitoring - -### Planned - -- VM creation from templates -- Snapshot management -- Console access -- Storage management -- Network configuration - -## Requirements - -- **PVE Version**: 6.x or 7.x -- **Access**: SSH access to PVE host -- **Permissions**: Appropriate PVE user roles -- **Network**: Connectivity to PVE API (via SSH) - -## Tips - -- Use **dedicated PVE user** with limited permissions -- Monitor **resource usage** for optimal performance -- Check **VM status** before maintenance -- Use **snapshots** before major changes diff --git a/docs/src/content/docs/features/snippets.md b/docs/src/content/docs/features/snippets.md deleted file mode 100644 index cd8e2635..00000000 --- a/docs/src/content/docs/features/snippets.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Snippets -description: Save and execute custom shell commands ---- - -Snippets allow you to save frequently used shell commands for quick execution. - -## Creating Snippets - -1. Go to the **Snippets** tab -2. Tap the **+** button -3. Fill in snippet details: - - **Name**: Friendly name for the snippet - - **Command**: The shell command to execute - - **Description**: Optional notes -4. Save the snippet - -## Using Snippets - -1. Open a server -2. Tap the **Snippet** button -3. Select a snippet to execute -4. View output in the terminal - -## Snippet Features - -- **Quick Execute**: One-tap command execution -- **Variables**: Use server-specific variables -- **Organization**: Group related snippets -- **Import/Export**: Share snippets between devices -- **Sync**: Optional cloud sync - -## Example Snippets - -### System Update -```bash -sudo apt update && sudo apt upgrade -y -``` - -### Disk Cleanup -```bash -sudo apt autoremove -y && sudo apt clean -``` - -### Docker Cleanup -```bash -docker system prune -a -``` - -### View System Logs -```bash -journalctl -n 50 -f -``` - -## Tips - -- Use **descriptive names** for easy identification -- Add **comments** for complex commands -- Test commands before saving as snippets -- Organize snippets by category or server type diff --git a/docs/src/content/docs/fr/advanced/bulk-import.md b/docs/src/content/docs/fr/advanced/bulk-import.md new file mode 100644 index 00000000..012ff1e3 --- /dev/null +++ b/docs/src/content/docs/fr/advanced/bulk-import.md @@ -0,0 +1,83 @@ +--- +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** (`keyId`) 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", + "keyId": "", + "tags": ["production"], + "autoConnect": false + } +] +``` + +## Champs + +| Champ | Requis | Description | +|-------|----------|-------------| +| `name` | Oui | Nom d'affichage | +| `ip` | Oui | Domaine ou adresse IP | +| `port` | Oui | Port SSH (généralement 22) | +| `user` | Oui | Nom d'utilisateur SSH | +| `pwd` | Non | Mot de passe (à éviter - utilisez plutôt des clés SSH) | +| `keyId` | Non | Nom de la clé SSH (à partir des clés privées - recommandé) | +| `tags` | Non | Tags d'organisation | +| `autoConnect` | Non | Connexion automatique au démarrage | + +## É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", + "keyId": "my-key", + "tags": ["production", "web"] + }, + { + "name": "Développement", + "ip": "dev.example.com", + "port": 2222, + "user": "dev", + "keyId": "dev-key", + "tags": ["development"] + } +] +``` + +## Conseils + +- **Utilisez des clés SSH** au lieu de mots de passe lorsque cela est possible +- **Testez la connexion** après l'importation +- **Organisez avec des tags** pour une gestion plus facile +- **Supprimez le fichier JSON** après l'importation +- **Ne validez jamais** de fichiers JSON contenant des identifiants dans le contrôle de version diff --git a/docs/src/content/docs/fr/advanced/custom-commands.md b/docs/src/content/docs/fr/advanced/custom-commands.md new file mode 100644 index 00000000..f0ae1e9e --- /dev/null +++ b/docs/src/content/docs/fr/advanced/custom-commands.md @@ -0,0 +1,72 @@ +--- +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" +} +``` + +**Exemple :** +```json +{ + "Mémoire": "free -h", + "Disque": "df -h", + "Uptime": "uptime" +} +``` + +## Visualisation des résultats + +Après la configuration, les commandes personnalisées apparaissent sur la page de détails du serveur et s'actualisent automatiquement. + +## Noms de commandes spéciaux + +### server_card_top_right + +Affichage sur la carte du serveur de la page d'accueil (coin supérieur droit) : + +```json +{ + "server_card_top_right": "votre-commande-ici" +} +``` + +## Conseils + +**Utilisez des chemins absolus :** +```json +{"Mon script": "/usr/local/bin/mon-script.sh"} +``` + +**Commandes avec pipe :** +```json +{"Processus principal": "ps aux | sort -rk 3 | head -5"} +``` + +**Formater la sortie :** +```json +{"Charge CPU": "uptime | awk -F'load average:' '{print $2}'"} +``` + +**Gardez les commandes rapides :** Moins de 5 secondes pour une meilleure expérience. + +**Limiter la sortie :** +```json +{"Logs": "tail -20 /var/log/syslog"} +``` + +## Sécurité + +Les commandes s'exécutent avec les permissions de l'utilisateur SSH. Évitez les commandes qui modifient l'état du système. diff --git a/docs/src/content/docs/fr/advanced/custom-logo.md b/docs/src/content/docs/fr/advanced/custom-logo.md new file mode 100644 index 00000000..faa0d8c6 --- /dev/null +++ b/docs/src/content/docs/fr/advanced/custom-logo.md @@ -0,0 +1,54 @@ +--- +title: Logo de serveur personnalisé +description: Utiliser des images personnalisées pour les cartes de serveur +--- + +Affichez des logos personnalisés sur les cartes de serveur à l'aide d'URL d'images. + +## Configuration + +1. Paramètres du serveur → Logo personnalisé +2. Entrez l'URL de l'image + +## Espaces réservés d'URL + +### {DIST} - Distribution Linux + +Remplacé automatiquement par la distribution détectée : + +``` +https://example.com/{DIST}.png +``` + +Devient : `debian.png`, `ubuntu.png`, `arch.png`, etc. + +### {BRIGHT} - Thème + +Remplacé automatiquement par le thème actuel : + +``` +https://example.com/{BRIGHT}.png +``` + +Devient : `light.png` ou `dark.png` + +### Combiner les deux + +``` +https://example.com/{DIST}-{BRIGHT}.png +``` + +Devient : `debian-light.png`, `ubuntu-dark.png`, etc. + +## Conseils + +- Utilisez les formats PNG ou SVG +- Taille recommandée : 64x64 à 128x128 pixels +- Utilisez des URL HTTPS +- Gardez des tailles de fichiers réduites + +## Distributions supportées + +debian, ubuntu, centos, fedora, opensuse, kali, alpine, arch, rocky, deepin, armbian, wrt + +Liste complète : [`dist.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/model/server/dist.dart) diff --git a/docs/src/content/docs/fr/advanced/json-settings.md b/docs/src/content/docs/fr/advanced/json-settings.md new file mode 100644 index 00000000..e0c8dec5 --- /dev/null +++ b/docs/src/content/docs/fr/advanced/json-settings.md @@ -0,0 +1,64 @@ +--- +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} +``` + +**Type :** entier | **Par défaut :** 5 | **Plage :** 1-60 + +### recordHistory + +Enregistrer l'historique (chemins SFTP, etc.). + +```json +{"recordHistory": true} +``` + +**Type :** booléen | **Par défaut :** true + +### textFactor + +Facteur de mise à l'échelle du texte. + +```json +{"textFactor": 1.2} +``` + +**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`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/store/setting.dart). + +Recherchez : +```dart +late final settingName = StoreProperty(box, 'settingKey', defaultValue); +``` + +## ⚠️ Important + +**Avant d'éditer :** +- **Créer une sauvegarde** - De mauvais paramètres peuvent empêcher l'ouverture de l'application +- **Éditer avec soin** - Le JSON doit être valide + +## Récupération + +Si l'application ne s'ouvre plus après l'édition : +1. Effacer les données de l'application (dernier recours) +2. Réinstaller l'application +3. Restaurer à partir d'une sauvegarde diff --git a/docs/src/content/docs/fr/advanced/troubleshooting.md b/docs/src/content/docs/fr/advanced/troubleshooting.md new file mode 100644 index 00000000..d1729be2 --- /dev/null +++ b/docs/src/content/docs/fr/advanced/troubleshooting.md @@ -0,0 +1,118 @@ +--- +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 + ``` + +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 diff --git a/docs/src/content/docs/fr/advanced/widgets.md b/docs/src/content/docs/fr/advanced/widgets.md new file mode 100644 index 00000000..179e7b13 --- /dev/null +++ b/docs/src/content/docs/fr/advanced/widgets.md @@ -0,0 +1,90 @@ +--- +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](https://github.com/lollipopkit/server_box_monitor) sur vos serveurs. + +## Prérequis + +Installez d'abord ServerBox Monitor sur votre serveur. Consultez le [Wiki de ServerBox Monitor](https://github.com/lollipopkit/server_box_monitor/wiki/Home) pour les instructions de configuration. + +Après l'installation, votre serveur doit avoir : +- Un point de terminaison HTTP/HTTPS +- Un point de terminaison API `/status` +- Une authentification facultative + +## Format de l'URL + +``` +https://votre-serveur.com/status +``` + +Doit se terminer par `/status`. + +## Widget iOS + +### Configuration + +1. Appuyez longuement sur l'écran d'accueil → Appuyez sur **+** +2. Recherchez "ServerBox" +3. Choisissez la taille du widget +4. Appuyez longuement sur le widget → **Modifier le widget** +5. Entrez l'URL se terminant par `/status` + +### Notes + +- Doit utiliser HTTPS (sauf pour les adresses IP locales) +- Taux de rafraîchissement maximal : 30 minutes (limite iOS) +- Ajoutez plusieurs widgets pour plusieurs serveurs + +## Widget Android + +### Configuration + +1. Appuyez longuement sur l'écran d'accueil → **Widgets** +2. Trouvez "ServerBox" → Ajoutez à l'écran d'accueil +3. Notez le numéro d'ID du widget affiché +4. Ouvrez l'application ServerBox → Paramètres +5. Appuyez sur **Configurer le lien du widget d'accueil** +6. Ajoutez l'entrée : `Widget ID` = `URL d'état` + +Exemple : +- Clé : `17` +- Valeur : `https://mon-serveur.com/status` + +7. Appuyez sur le widget sur l'écran d'accueil pour le rafraîchir + +## Widget watchOS + +### Configuration + +1. Ouvrez l'application iPhone → Paramètres +2. **Paramètres iOS** → **Application Watch** +3. Appuyez sur **Ajouter une URL** +4. Entrez l'URL se terminant par `/status` +5. Attendez que l'application de la montre se synchronise + +### Notes + +- Essayez de redémarrer l'application de la montre si elle ne se met pas à jour +- Vérifiez que le téléphone et la montre sont connectés + +## Dépannage + +### Le widget ne se met pas à jour + +**iOS :** Attendez jusqu'à 30 minutes, puis supprimez et rajoutez-le. +**Android :** Appuyez sur le widget pour forcer le rafraîchissement, vérifiez l'ID dans les paramètres. +**watchOS :** Redémarrez l'application de la montre, attendez quelques minutes. + +### Le widget affiche une erreur + +- Vérifiez que ServerBox Monitor fonctionne +- Testez l'URL dans un navigateur +- Vérifiez que l'URL se termine par `/status` + +## Sécurité + +- **Utilisez toujours HTTPS** si possible +- **Adresses IP locales uniquement** sur les réseaux de confiance diff --git a/docs/src/content/docs/fr/development/architecture.md b/docs/src/content/docs/fr/development/architecture.md new file mode 100644 index 00000000..7e2fa2ab --- /dev/null +++ b/docs/src/content/docs/fr/development/architecture.md @@ -0,0 +1,86 @@ +--- +title: Architecture +description: Modèles d'architecture et décisions de conception +--- + +Server Box suit les principes de la Clean Architecture avec une séparation claire entre les couches de données, de domaine et de présentation. + +## Architecture en couches + +``` +┌─────────────────────────────────────┐ +│ Couche Présentation │ +│ (lib/view/page/) │ +│ - Pages, Widgets, Contrôleurs │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Couche Logique Métier │ +│ (lib/data/provider/) │ +│ - Providers Riverpod │ +│ - Gestion de l'état │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ Couche Données │ +│ (lib/data/model/, store/) │ +│ - Modèles, Stockage, Services │ +└─────────────────────────────────────┘ +``` + +## Modèles clés + +### Gestion de l'état : Riverpod + +- **Génération de code** : Utilise `riverpod_generator` pour des providers type-safe +- **State Notifiers** : Pour un état mutable avec une logique métier +- **Async Notifiers** : Pour les états de chargement et d'erreur +- **Stream Providers** : Pour les données en temps réel + +### Modèles immuables : Freezed + +- Tous les modèles de données utilisent Freezed pour l'immuabilité +- Types Union pour la représentation de l'état +- Sérialisation JSON intégrée +- Extensions CopyWith pour les mises à jour + +### Stockage local : Hive + +- **hive_ce** : Édition communautaire de Hive +- Pas de `@HiveField` ou `@HiveType` manuel requis +- Adaptateurs de type auto-générés +- Stockage clé-valeur persistant + +## Injection de dépendances + +Les services et les stores sont injectés via : + +1. **Providers** : Exposer les dépendances à l'UI +2. **GetIt** : Localisation de services (le cas échéant) +3. **Injection par constructeur** : Dépendances explicites + +## Flux de données + +``` +Action Utilisateur → Widget → Provider → Service/Store → Mise à jour Modèle → Reconstruction 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 diff --git a/docs/src/content/docs/fr/development/building.md b/docs/src/content/docs/fr/development/building.md new file mode 100644 index 00000000..e6a96e49 --- /dev/null +++ b/docs/src/content/docs/fr/development/building.md @@ -0,0 +1,116 @@ +--- +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 +``` + +## Construction pour la production + +Le projet utilise `fl_build` pour la construction : + +```bash +# Construire pour une plateforme spécifique +dart run fl_build -p + +# 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 diff --git a/docs/src/content/docs/fr/development/codegen.md b/docs/src/content/docs/fr/development/codegen.md new file mode 100644 index 00000000..d2e81c00 --- /dev/null +++ b/docs/src/content/docs/fr/development/codegen.md @@ -0,0 +1,98 @@ +--- +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 et régénérer +dart run build_runner build --delete-conflicting-outputs --clean +``` + +## 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 json) => + _$ServerFromJson(json); + Map 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 +- Ajoutez les fichiers générés au `.gitignore` +- Ne modifiez jamais manuellement les fichiers générés diff --git a/docs/src/content/docs/fr/development/state.md b/docs/src/content/docs/fr/development/state.md new file mode 100644 index 00000000..591de422 --- /dev/null +++ b/docs/src/content/docs/fr/development/state.md @@ -0,0 +1,115 @@ +--- +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 build(Server server) async { + return fetchStatus(server); + } + + Future 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(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 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); +``` diff --git a/docs/src/content/docs/fr/development/structure.md b/docs/src/content/docs/fr/development/structure.md new file mode 100644 index 00000000..3d299362 --- /dev/null +++ b/docs/src/content/docs/fr/development/structure.md @@ -0,0 +1,96 @@ +--- +title: Structure du projet +description: Comprendre la base de code de Server Box +--- + +Le projet Server Box suit une architecture modulaire avec une séparation claire des préoccupations. + +## Structure des répertoires + +``` +lib/ +├── core/ # Utilitaires de base et extensions +├── data/ # Couche de données +│ ├── model/ # Modèles de données par fonctionnalité +│ ├── provider/ # Providers Riverpod +│ └── store/ # Stockage local (Hive) +├── view/ # Couche UI +│ ├── page/ # Pages principales +│ └── widget/ # Widgets réutilisables +├── generated/ # Localisation générée +├── l10n/ # Fichiers ARB de localisation +└── hive/ # Adaptateurs 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 diff --git a/docs/src/content/docs/fr/development/testing.md b/docs/src/content/docs/fr/development/testing.md new file mode 100644 index 00000000..dac0d4ef --- /dev/null +++ b/docs/src/content/docs/fr/development/testing.md @@ -0,0 +1,113 @@ +--- +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 sont situés dans le répertoire `test/`, reflétant la structure de `lib/` : + +``` +test/ +├── data/ +│ ├── model/ +│ └── provider/ +├── view/ +│ └── widget/ +└── test_helpers.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()); +}); +``` + +## Mocking (Simulations) + +Utiliser des mocks pour les dépendances externes : + +```dart +class MockSshService extends Mock implements SshService {} + +test('se connecte au serveur', () async { + final mockSsh = MockSshService(); + when(mockSsh.connect(any)).thenAnswer((_) async => true); + + // Tester avec le mock +}); +``` + +## Tests d'intégration + +Tester des flux utilisateurs complets (dans `integration_test/`) : + +```dart +testWidgets('flux d\'ajout de serveur', (tester) async { + await tester.pumpWidget(MyApp()); + + // Appuyer sur le bouton d'ajout + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + + // Remplir le formulaire + await tester.enterText(find.byKey(Key('name')), 'Test Server'); + // ... +}); +``` + +## Bonnes pratiques + +1. **Arrange-Act-Assert** : Structurer les tests clairement +2. **Noms descriptifs** : Les noms de tests doivent décrire le comportement +3. **Une assertion par test** : Garder les tests focalisés +4. **Mocker les dépendances externes** : Ne pas dépendre de serveurs réels +5. **Tester les cas limites** : Listes vides, valeurs nulles, etc. diff --git a/docs/src/content/docs/fr/index.mdx b/docs/src/content/docs/fr/index.mdx new file mode 100644 index 00000000..a5fdf0cd --- /dev/null +++ b/docs/src/content/docs/fr/index.mdx @@ -0,0 +1,46 @@ +--- +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: /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 + + + + 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. + + + Terminal SSH complet avec support multi-onglets et clavier virtuel pour les appareils mobiles. + + + Gérez les fichiers sur vos serveurs avec le client SFTP intégré et le navigateur de fichiers local. + + + Démarrez, arrêtez et surveillez les conteneurs Docker avec une interface intuitive. + + + Disponible sur iOS, Android, macOS, Linux, Windows et watchOS. + + + Support complet de localisation incluant l'anglais, le chinois, l'allemand, le français et plus encore. + + + +## Liens rapides + +- **Téléchargement**: Disponible sur l'[App Store](https://apps.apple.com/app/id1586449703), [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) et [F-Droid](https://f-droid.org/) +- **Documentation**: Explorez les guides pour commencer avec Server Box +- **Support**: Rejoignez notre communauté sur GitHub pour des discussions et des problèmes diff --git a/docs/src/content/docs/fr/installation.mdx b/docs/src/content/docs/fr/installation.mdx new file mode 100644 index 00000000..7f2db053 --- /dev/null +++ b/docs/src/content/docs/fr/installation.mdx @@ -0,0 +1,51 @@ +--- +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](https://apps.apple.com/app/id1586449703)**. + +### Android + +Choisissez votre source préférée : + +- **[F-Droid](https://f-droid.org/)** - Pour les utilisateurs qui préfèrent les sources exclusivement FOSS +- **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** - Pour la dernière version directement depuis la source + +## Applications de Bureau + +### macOS + +Téléchargez depuis les **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +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](https://github.com/lollipopkit/flutter_server_box/releases)**. + +Disponible en packages AppImage, deb ou tar.gz. + +### Windows + +Téléchargez depuis les **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. + +## watchOS + +Disponible sur l'**[App Store](https://apps.apple.com/app/id1586449703)** en tant que partie de l'application iOS. + +## Construction à partir des sources + +Pour construire Server Box à partir des sources, consultez la section [Construction](/fr/development/building/) dans la documentation de développement. + +## Informations sur la version + +Consultez la page [GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases) pour la dernière version et le journal des modifications (changelog). diff --git a/docs/src/content/docs/fr/introduction.mdx b/docs/src/content/docs/fr/introduction.mdx new file mode 100644 index 00000000..15b9426d --- /dev/null +++ b/docs/src/content/docs/fr/introduction.mdx @@ -0,0 +1,32 @@ +--- +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](https://github.com/lollipopkit/flutter_server_box). diff --git a/docs/src/content/docs/fr/platforms/desktop.md b/docs/src/content/docs/fr/platforms/desktop.md new file mode 100644 index 00000000..2ccadbd6 --- /dev/null +++ b/docs/src/content/docs/fr/platforms/desktop.md @@ -0,0 +1,80 @@ +--- +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 diff --git a/docs/src/content/docs/fr/platforms/mobile.md b/docs/src/content/docs/fr/platforms/mobile.md new file mode 100644 index 00000000..22162bc2 --- /dev/null +++ b/docs/src/content/docs/fr/platforms/mobile.md @@ -0,0 +1,77 @@ +--- +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 diff --git a/docs/src/content/docs/fr/principles/architecture.md b/docs/src/content/docs/fr/principles/architecture.md new file mode 100644 index 00000000..a06762dc --- /dev/null +++ b/docs/src/content/docs/fr/principles/architecture.md @@ -0,0 +1,214 @@ +--- +title: Présentation de l'architecture +description: Architecture de haut niveau de l'application +--- + +Server Box suit une architecture en couches avec une séparation claire des préoccupations. + +## Couches architecturales + +``` +┌─────────────────────────────────────────────────┐ +│ Couche de présentation (UI) │ +│ lib/view/page/, lib/view/widget/ │ +│ - Pages, Widgets, Contrôleurs │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Couche logique métier │ +│ lib/data/provider/ │ +│ - Riverpod Providers, State Notifiers │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Couche d'accès aux données │ +│ lib/data/store/, lib/data/model/ │ +│ - Hive Stores, Modèles de données │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ Couche d'intégration externe │ +│ - SSH (dartssh2), Terminal (xterm), SFTP │ +│ - Code spécifique à la plateforme (iOS, etc.) │ +└─────────────────────────────────────────────────┘ +``` + +## Fondations de l'application + +### Point d'entrée principal + +`lib/main.dart` initialise l'application : + +```dart +void main() { + runApp( + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +### Widget racine + +`MyApp` fournit : +- **Gestion des thèmes** : Commutation entre thèmes clair/sombre +- **Configuration du routage** : Structure de navigation +- **Provider Scope** : Racine de l'injection de dépendances + +### Page d'accueil + +`HomePage` sert de plaque tournante pour la navigation : +- **Interface par onglets** : Serveur, Snippet, Conteneur, SSH +- **Gestion de l'état** : État par onglet +- **Navigation** : Accès aux fonctionnalités + +## Systèmes de base + +### Gestion de l'état : Riverpod + +**Pourquoi Riverpod ?** +- Sécurité au moment de la compilation +- Tests faciles +- Pas de dépendance au Build context +- Fonctionne sur toutes les plateformes + +**Types de Provider utilisés :** +- `StateProvider` : État mutable simple +- `AsyncNotifierProvider` : États de chargement/erreur/données +- `StreamProvider` : Flux de données en temps réel +- Future providers : Opérations asynchrones uniques + +### Persistance des données : Hive CE + +**Pourquoi Hive CE ?** +- Pas de dépendances de code natif +- Stockage clé-valeur rapide +- Type-safe avec génération de code +- Pas d'annotations de champs manuelles requises + +**Stores :** +- `SettingStore` : Préférences de l'application +- `ServerStore` : Configurations de serveur +- `SnippetStore` : Extraits de commande +- `KeyStore` : Clés SSH + +### Modèles immuables : Freezed + +**Avantages :** +- Immuabilité au moment de la compilation +- Types Union pour l'état +- Sérialisation JSON intégrée +- Extensions CopyWith + +## Stratégie multiplateforme + +### Système de plugins + +Les plugins Flutter permettent l'intégration avec les plateformes : + +| Plateforme | Méthode d'intégration | +|------------|----------------------| +| iOS | CocoaPods, Swift/Obj-C | +| Android | Gradle, Kotlin/Java | +| macOS | CocoaPods, Swift | +| Linux | CMake, C++ | +| Windows | CMake, C# | + +### Fonctionnalités spécifiques aux plateformes + +**iOS uniquement :** +- Widgets de l'écran d'accueil +- Activités en direct (Live Activities) +- Compagnon Apple Watch + +**Android uniquement :** +- Service en arrière-plan +- Notifications push +- Accès au système de fichiers + +**Bureau uniquement :** +- Intégration de la barre de menus +- Fenêtres multiples +- Barre de titre personnalisée + +## Dépendances personnalisées + +### Fork de dartssh2 + +Client SSH amélioré avec : +- Meilleur support mobile +- Gestion des erreurs améliorée +- Optimisations de performance + +### Fork de xterm.dart + +Émulateur de terminal avec : +- Rendu optimisé pour le mobile +- Support des gestes tactiles +- Intégration du clavier virtuel + +### fl_lib + +Paquet d'utilitaires partagés avec : +- Widgets communs +- Extensions +- Fonctions d'aide + +## Système de construction + +### Paquet fl_build + +Système de construction personnalisé pour : +- Constructions multiplateformes +- Signature de code +- Regroupement des ressources (assets) +- Gestion des versions + +### Processus de construction + +``` +make.dart (version) → fl_build (build) → Sortie plateforme +``` + +1. **Pré-construction** : Calculer la version à partir de Git +2. **Construction** : Compiler pour la plateforme cible +3. **Post-construction** : Paqueter et 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 +``` + +### Flux d'action utilisateur + +``` +1. L'utilisateur appuie sur un bouton → +2. Le Widget appelle une méthode du provider → +3. Le Provider met à jour l'état → +4. Le changement d'état déclenche la reconstruction → +5. Le nouvel état est reflété dans l'UI +``` + +## Architecture de sécurité + +### Protection des données + +- **Mots de passe** : Chiffrés avec flutter_secure_storage +- **Clés SSH** : Chiffrées au repos +- **Empreintes d'hôte** : Stockées de manière sécurisée +- **Données de session** : Non persistées + +### Sécurité de la connexion + +- **Vérification de la clé d'hôte** : Détection MITM (homme du milieu) +- **Chiffrement** : Chiffrement SSH standard +- **Pas de texte clair** : Les données sensibles ne sont jamais stockées en clair diff --git a/docs/src/content/docs/fr/principles/sftp.md b/docs/src/content/docs/fr/principles/sftp.md new file mode 100644 index 00000000..4924ba28 --- /dev/null +++ b/docs/src/content/docs/fr/principles/sftp.md @@ -0,0 +1,490 @@ +--- +title: Système SFTP +description: Comment fonctionne le navigateur de fichiers SFTP +--- + +Le système SFTP fournit des capacités de gestion de fichiers via SSH. + +## Architecture + +``` +┌─────────────────────────────────────────────┐ +│ Couche UI SFTP │ +│ - Navigateur de fichiers (distant) │ +│ - Navigateur de fichiers (local) │ +│ - File d'attente de transfert │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Gestion de l'état SFTP │ +│ - sftpProvider │ +│ - Gestion des chemins │ +│ - File d'attente d'opérations │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Couche protocole SFTP │ +│ - Sous-système SSH │ +│ - Opérations sur les fichiers │ +│ - Liste des répertoires │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Transport SSH │ +│ - Canal sécurisé │ +│ - Streaming de données │ +└─────────────────────────────────────────────┘ +``` + +## Établissement de la connexion + +### Création du client SFTP + +```dart +Future createSftpClient(Spi spi) async { + // 1. Obtenir le client SSH (réutiliser si disponible) + final sshClient = await genClient(spi); + + // 2. Ouvrir le sous-système SFTP + final sftp = await sshClient.openSftp(); + + return sftp; +} +``` + +### Réutilisation de la connexion + +SFTP réutilise les connexions SSH existantes : + +```dart +class ServerProvider { + SSHClient? _sshClient; + SftpClient? _sftpClient; + + Future getSftpClient(String spiId) async { + _sftpClient ??= await _sshClient!.openSftp(); + return _sftpClient!; + } +} +``` + +## Opérations du système de fichiers + +### Liste des répertoires + +```dart +Future> listDirectory(String path) async { + final sftp = await getSftpClient(spiId); + + // Lister le répertoire + final files = await sftp.listDir(path); + + // Trier selon les paramètres + files.sort((a, b) { + switch (sortOption) { + case SortOption.name: + return a.name.toLowerCase().compareTo(b.name.toLowerCase()); + case SortOption.size: + return a.size.compareTo(b.size); + case SortOption.time: + return a.modified.compareTo(b.modified); + } + }); + + // Dossiers en premier si activé + if (showFoldersFirst) { + final dirs = files.where((f) => f.isDirectory); + final regular = files.where((f) => !f.isDirectory); + return [...dirs, ...regular]; + } + + return files; +} +``` + +### Métadonnées de fichiers + +```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); +} +``` + +## Opérations sur les fichiers + +### Téléversement (Upload) + +```dart +Future uploadFile( + String localPath, + String remotePath, +) async { + final sftp = await getSftpClient(spiId); + + // Créer la requête + final req = SftpReq( + spi: spi, + remotePath: remotePath, + localPath: localPath, + type: SftpReqType.upload, + ); + + // Ajouter à la file d'attente + _transferQueue.add(req); + + // Exécuter le transfert avec progression + final file = File(localPath); + final size = await file.length(); + final stream = file.openRead(); + + await sftp.upload( + stream: stream, + toPath: remotePath, + onProgress: (transferred) { + _updateProgress(req, transferred, size); + }, + ); + + // Terminé + _transferQueue.remove(req); +} +``` + +### Téléchargement (Download) + +```dart +Future downloadFile( + String remotePath, + String localPath, +) async { + final sftp = await getSftpClient(spiId); + + // Créer le fichier local + final file = File(localPath); + final sink = file.openWrite(); + + // Télécharger avec progression + final stat = await sftp.stat(remotePath); + + await sftp.download( + fromPath: remotePath, + toSink: sink, + onProgress: (transferred) { + _updateProgress( + SftpReq(...), + transferred, + stat.size, + ); + }, + ); + + await sink.close(); +} +``` + +### Édition des permissions + +```dart +Future setPermissions( + String path, + String permissions, +) async { + final sftp = await getSftpClient(spiId); + + // Analyser les permissions (ex: "rwxr-xr-x" ou "755") + final mode = parsePermissions(permissions); + + // Définir via commande SSH (plus fiable que SFTP) + final ssh = await getSshClient(spiId); + await ssh.exec('chmod $mode "$path"'); +} +``` + +## Gestion des chemins + +### Structure de chemin + +```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, + ); + } +} +``` + +### Historique de navigation + +```dart +class PathHistory { + final List _history = []; + int _index = -1; + + void push(String path) { + // Supprimer l'historique suivant + _history.removeRange(_index + 1, _history.length); + _history.add(path); + _index = _history.length - 1; + } + + String? back() { + if (_index > 0) { + _index--; + return _history[_index]; + } + return null; + } + + String? forward() { + if (_index < _history.length - 1) { + _index++; + return _history[_index]; + } + return null; + } +} +``` + +## Système de transfert + +### Requête de transfert + +```dart +class SftpReq { + final Spi spi; + final String remotePath; + final String localPath; + final SftpReqType type; + final DateTime createdAt; + + int? totalBytes; + int? transferredBytes; + String? error; +} +``` + +### Suivi de progression + +```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); + } +} +``` + +### Gestion de la file d'attente + +```dart +class TransferQueue { + final List _queue = []; + final Map _progress = {}; + int _concurrent = 3; // Nombre max de transferts simultanés + + Future 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 _executeTransfer(SftpReq req) async { + try { + _progress[req.id] = TransferProgress.inProgress(req); + + if (req.type == SftpReqType.upload) { + await uploadFile(req.localPath, req.remotePath); + } else { + await downloadFile(req.remotePath, req.localPath); + } + + _progress[req.id] = TransferProgress.completed(req); + } catch (e) { + _progress[req.id] = TransferProgress.failed(req, e); + } + } +} +``` + +## Modèle de stockage local + +### Cache de téléchargement + +Fichiers téléchargés stockés sur : + +```dart +String getLocalDownloadPath(String spiId, String remotePath) { + final normalized = remotePath.replaceAll('/', '_'); + return 'Paths.file/$spiId/$normalized'; +} +``` + +Exemple : +- Distant : `/var/log/nginx/access.log` +- spiId : `server-123` +- Local : `Paths.file/server-123/_var_log_nginx_access.log` + +## Édition de fichiers + +### Flux d'édition + +```dart +Future editFile(String path) async { + final sftp = await getSftpClient(spiId); + + // 1. Vérifier la taille + final stat = await sftp.stat(path); + if (stat.size > editorMaxSize) { + showWarning('Fichier trop volumineux pour l\'éditeur intégré'); + return; + } + + // 2. Télécharger vers dossier temp + final temp = await downloadToTemp(path); + + // 3. Ouvrir dans l'éditeur + final content = await openEditor(temp.path); + + // 4. Téléverser en retour + await uploadFile(temp.path, path); + + // 5. Nettoyage + await temp.delete(); +} +``` + +### Intégration d'un éditeur externe + +```dart +Future editInExternalEditor(String path) async { + final ssh = await getSshClient(spiId); + + // Ouvrir le terminal avec l'éditeur + final editor = getSetting('sftpEditor', 'vim'); + await ssh.exec('$editor "$path"'); + + // L'utilisateur édite dans le terminal + // Après sauvegarde, rafraîchir la vue SFTP +} +``` + +## Gestion des erreurs + +### Erreurs de permission + +```dart +try { + await sftp.upload(...); +} on SftpPermissionException { + showError('Permission refusée : ${stat.path}'); + showHint('Vérifiez les permissions et la propriété du fichier'); +} +``` + +### Erreurs de connexion + +```dart +try { + await sftp.listDir(path); +} on SftpConnectionException { + showError('Connexion perdue'); + await reconnect(); +} +``` + +### Erreurs d'espace disque + +```dart +try { + await sftp.upload(...); +} on SftpNoSpaceException { + showError('Disque plein sur le serveur distant'); +} +``` + +## Optimisations de performance + +### Cache de répertoire + +```dart +class DirectoryCache { + final Map _cache = {}; + final Duration ttl = Duration(minutes: 5); + + Future> list(String path) async { + final cached = _cache[path]; + if (cached != null && !cached.isExpired) { + return cached.files; + } + + final files = await sftp.listDir(path); + _cache[path] = CachedDirectory(files); + return files; + } +} +``` + +### Chargement différé (Lazy Loading) + +Pour les répertoires volumineux (>1000 éléments) : + +```dart +List loadPage(String path, int page, int pageSize) { + final all = cache[path] ?? []; + final start = page * pageSize; + final end = start + pageSize; + return all.sublist(start, end.clamp(0, all.length)); +} +``` + +### Pagination + +```dart +class PaginatedDirectory { + static const pageSize = 100; + + Future> getPage(int page) async { + final offset = page * pageSize; + return await sftp.listDir( + path, + offset: offset, + limit: pageSize, + ); + } +} +``` diff --git a/docs/src/content/docs/fr/principles/ssh.md b/docs/src/content/docs/fr/principles/ssh.md new file mode 100644 index 00000000..8b5015d0 --- /dev/null +++ b/docs/src/content/docs/fr/principles/ssh.md @@ -0,0 +1,305 @@ +--- +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 +``` + +### É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 +} +``` + +### Étape 2 : Génération du client + +`genClient(spi)` crée le client SSH : + +```dart +Future genClient(Spi spi) async { + // 1. Établir le socket + var socket = await connect(spi.ip, spi.port); + + // 2. Essayer l'URL alternative en cas d'échec + if (socket == null && spi.alterUrl != null) { + socket = await connect(spi.alterUrl, spi.port); + } + + if (socket == null) { + throw ConnectionException('Unable to connect'); + } + + // 3. Authentifier + final client = SSHClient( + socket: socket, + username: spi.user, + onPasswordRequest: () => spi.pwd, + onIdentityRequest: () => loadKey(spi.keyId), + ); + + // 4. Vérifier la clé d'hôte + await verifyHostKey(client, spi); + + return client; +} +``` + +### Étape 3 : Serveur de rebond (si configuré) + +Pour les serveurs de rebond, connexion récursive : + +```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é +} +``` + +## Méthodes d'authentification + +### Authentification par mot de passe + +```dart +onPasswordRequest: () => spi.pwd +``` + +- 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); +} +``` + +**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; +} +``` + +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} +``` + +Exemple : +```text +mon-serveur::ssh-ed25519 +mon-serveur::ecdsa-sha2-nistp256 +``` + +### Formats d'empreinte + +**MD5 Hex :** +```text +aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99 +``` + +**Base64 :** +```text +SHA256:AbCdEf1234567890...= +``` + +### Flux de vérification + +```dart +Future verifyHostKey(SSHClient client, Spi spi) async { + final key = await client.hostKey; + final keyType = key.type; + final fingerprint = md5Hex(key); // ou base64 + + final stored = SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType']; + + if (stored == null) { + // Nouvel hôte - inviter l'utilisateur + final trust = await promptUser( + 'Hôte inconnu', + 'Empreinte : $fingerprint', + ); + if (trust) { + SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType'] = fingerprint; + } + } else if (stored != fingerprint) { + // Modifié - avertir l'utilisateur + await warnUser( + 'La clé d\'hôte a changé !', + 'Attaque MITM possible', + ); + } +} +``` + +## Gestion des sessions + +### Mise en commun des connexions (Pooling) + +Clients actifs maintenus dans `ServerProvider` : + +```dart +class ServerProvider { + final Map _clients = {}; + + SSHClient getClient(String spiId) { + return _clients[spiId] ??= connect(spiId); + } +} +``` + +### Keep-Alive + +Maintenir la connexion pendant l'inactivité : + +```dart +Timer.periodic( + Duration(seconds: 30), + (_) => client.sendKeepAlive(), +); +``` + +### Reconnexion automatique + +En cas de perte de connexion : + +```dart +client.onError.listen((error) async { + await Future.delayed(Duration(seconds: 5)); + reconnect(); +}); +``` + +## Cycle de vie de la connexion + +```text +┌─────────────┐ +│ Initial │ +└──────┬──────┘ + │ connect() + ↓ +┌─────────────┐ +│ Connexion │ ←──┐ +└──────┬──────┘ │ + │ succès │ + ↓ │ échec (retry) +┌─────────────┐ │ +│ Connecté │───┘ +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ Actif │ ──→ Envoyer des commandes +└──────┬──────┘ + │ + ↓ (erreur/déconnexion) +┌─────────────┐ +│ Déconnecté │ +└─────────────┘ +``` + +## Gestion des erreurs + +### Délai d'attente de connexion (Timeout) + +```dart +try { + await client.connect().timeout( + Duration(seconds: 30), + ); +} on TimeoutException { + throw ConnectionException('Délai d\'attente de connexion dépassé'); +} +``` + +### É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'; +} +``` + +### Discordance de clé d'hôte + +```dart +onHostKeyMismatch: (stored, current) { + showSecurityWarning( + 'La clé d\'hôte a changé !', + 'Attaque MITM possible', + ); +} +``` + +## Considérations de performance + +### Réutilisation de la connexion + +- Réutiliser les clients entre les fonctionnalités +- Ne pas déconnecter/reconnecter inutilement +- Mutualiser les connexions pour les opérations simultanées + +### Paramètres optimaux + +- **Timeout** : 30 secondes (ajustable) +- **Keep-alive** : Toutes les 30 secondes +- **Délai de relecture** : 5 secondes + +### Efficacité du réseau + +- Connexion unique pour plusieurs opérations +- Commandes en pipeline si possible +- Éviter d'ouvrir plusieurs connexions diff --git a/docs/src/content/docs/fr/principles/state.md b/docs/src/content/docs/fr/principles/state.md new file mode 100644 index 00000000..55b994ac --- /dev/null +++ b/docs/src/content/docs/fr/principles/state.md @@ -0,0 +1,167 @@ +--- +title: Gestion de l'état +description: Comment l'état est géré avec Riverpod +--- + +Comprendre l'architecture de gestion de l'état dans Server Box. + +## Pourquoi Riverpod ? + +**Avantages clés :** +- **Sécurité à la compilation** : Capture les erreurs lors de la compilation +- **Pas de BuildContext requis** : Accès à l'état n'importe où +- **Tests faciles** : Simple de tester les providers de manière isolée +- **Génération de code** : Moins de code répétitif, type-safe + +## Architecture des Providers + +``` +┌─────────────────────────────────────────────┐ +│ Couche UI (Widgets) │ +│ - ConsumerWidget / ConsumerStatefulWidget │ +│ - ref.watch() / ref.read() │ +└─────────────────────────────────────────────┘ + ↓ observe (watches) +┌─────────────────────────────────────────────┐ +│ Couche Provider │ +│ - Annotations @riverpod │ +│ - Fichiers *.g.dart générés │ +└─────────────────────────────────────────────┘ + ↓ utilise (uses) +┌─────────────────────────────────────────────┐ +│ Couche Service / Store │ +│ - Logique métier │ +│ - Accès aux données │ +└─────────────────────────────────────────────┘ +``` + +## Types de Providers utilisés + +### 1. StateProvider (État simple) + +Pour un état simple et observable : + +```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 + } +} +``` + +**Utilisation :** +```dart +class MyWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themeNotifierProvider); + return Text('Thème : $theme'); + } +} +``` + +### 2. AsyncNotifierProvider (État asynchrone) + +Pour les données qui se chargent de manière asynchrone : + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + // Chargement initial + return await fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + return await fetchStatus(server); + }); + } +} +``` + +**Utilisation :** +```dart +final status = ref.watch(serverStatusProvider(server)); + +status.when( + data: (data) => StatusWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 3. StreamProvider (Données en temps réel) + +Pour les flux de données continus : + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + final client = ref.watch(sshClientProvider(server)); + final stream = client.monitorCpu(); + + // Libération automatique des ressources quand non observé + ref.onDispose(() { + client.stopMonitoring(); + }); + + return stream; +} +``` + +**Utilisation :** +```dart +final cpu = ref.watch(cpuUsageProvider(server)); + +cpu.when( + data: (usage) => CpuChart(usage), + loading: () => CircularProgressIndicator(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 4. Family Providers (Paramétrés) + +Providers qui acceptent des paramètres : + +```dart +@riverpod +Future> containers(ContainersRef ref, Server server) async { + final client = await ref.watch(sshClientProvider(server).future); + return await client.listContainers(); +} +``` + +**Utilisation :** +```dart +final containers = ref.watch(containersProvider(server)); + +// Différents serveurs = différents états mis en cache +final containers2 = ref.watch(containersProvider(server2)); +``` + +## Optimisations de performance + +- **Provider Keep-Alive** : Utilisez `@Riverpod(keepAlive: true)` pour empêcher la destruction automatique quand il n'y a plus d'écouteurs. +- **Observation sélective** : Utilisez `select` pour n'observer qu'une partie spécifique de l'état. +- **Mise en cache des Providers** : Les Family providers mettent en cache les résultats par paramètre. + +## Bonnes pratiques + +1. **Co-localiser les providers** : Placez-les près des widgets qui les consomment. +2. **Utiliser la génération de code** : Utilisez toujours `@riverpod`. +3. **Garder les providers focalisés** : Responsabilité unique. +4. **Gérer les états de chargement** : Gérez toujours les états AsyncValue. +5. **Libérer les ressources** : Utilisez `ref.onDispose()` pour le nettoyage. +6. **Éviter les arbres de providers profonds** : Gardez le graphe des providers plat. diff --git a/docs/src/content/docs/fr/principles/terminal.md b/docs/src/content/docs/fr/principles/terminal.md new file mode 100644 index 00000000..7bef17a0 --- /dev/null +++ b/docs/src/content/docs/fr/principles/terminal.md @@ -0,0 +1,198 @@ +--- +title: Implémentation du terminal +description: Comment le terminal SSH fonctionne en interne +--- + +Le terminal SSH est l'une des fonctionnalités les plus complexes, basée sur un fork personnalisé de xterm.dart. + +## Présentation de l'architecture + +``` +┌─────────────────────────────────────────────┐ +│ Couche UI du terminal │ +│ - Gestion des onglets │ +│ - Clavier virtuel │ +│ - Sélection de texte │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Émulateur xterm.dart │ +│ - PTY (Pseudo Terminal) │ +│ - Émulation VT100/ANSI │ +│ - Moteur de rendu │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Couche client SSH │ +│ - Session SSH │ +│ - Gestion des canaux │ +│ - Streaming de données │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ Serveur distant │ +│ - Processus Shell │ +│ - Exécution de commandes │ +└─────────────────────────────────────────────┘ +``` + +## Cycle de vie d'une session de terminal + +### 1. Création de la session + +```dart +Future createSession(Spi spi) async { + // 1. Obtenir le client SSH + final client = await genClient(spi); + + // 2. Créer le PTY + final pty = await client.openPty( + term: 'xterm-256color', + cols: 80, + rows: 24, + ); + + // 3. Initialiser l'émulateur de terminal + final terminal = Terminal( + backend: PtyBackend(pty), + ); + + // 4. Configurer le gestionnaire de redimensionnement + terminal.onResize.listen((size) { + pty.resize(size.cols, size.rows); + }); + + return TerminalSession( + terminal: terminal, + pty: pty, + client: client, + ); +} +``` + +### 2. Émulation de terminal + +Le fork xterm.dart fournit : + +**Émulation VT100/ANSI :** +- Mouvement du curseur +- Couleurs (support 256 couleurs) +- Attributs de texte (gras, souligné, etc.) +- Régions de défilement +- Tampon d'écran alterné + +**Rendu :** +- Rendu basé sur les lignes +- Support du texte bidirectionnel +- Support Unicode/emoji +- Redessins optimisés + +### 3. Flux de données + +``` +Entrée utilisateur + ↓ +Clavier virtuel / Clavier physique + ↓ +Émulateur de terminal (touche → séquence d'échappement) + ↓ +Canal SSH (envoi) + ↓ +PTY distant + ↓ +Shell distant + ↓ +Sortie de commande + ↓ +Canal SSH (réception) + ↓ +Émulateur de terminal (analyse des codes ANSI) + ↓ +Rendu à l'écran +``` + +## Système multi-onglets + +### Gestion des onglets + +Les onglets maintiennent leur état lors de la navigation : +- Connexion SSH maintenue active +- État du terminal préservé +- Tampon de défilement conservé +- Historique de saisie retenu + +## Clavier virtuel + +### Implémentation spécifique à la plateforme + +**iOS :** +- Clavier personnalisé basé sur UIView +- Basculable avec un bouton clavier +- Affichage/masquage automatique basé sur le focus + +**Android :** +- Méthode de saisie personnalisée +- Intégré au clavier système +- Boutons d'action rapide + +### Boutons du clavier + +| Bouton | Action | +|--------|--------| +| **Basculer** | Afficher/masquer le clavier système | +| **Ctrl** | Envoyer le modificateur Ctrl | +| **Alt** | Envoyer le modificateur Alt | +| **SFTP** | Ouvrir le répertoire courant | +| **Presse-papiers** | Copier/Coller contextuel | +| **Snippets** | Exécuter un extrait de code | + +## Sélection de texte + +1. **Appui long** : Entrer en mode sélection +2. **Glisser** : Étendre la sélection +3. **Relâcher** : Copier dans le presse-papiers + +## Police et dimensions + +### Calcul de la taille + +```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()); + } +} +``` + +### Pincer pour zoomer (Pinch-to-Zoom) + +```dart +GestureDetector( + onScaleStart: () => _baseFontSize = currentFontSize, + onScaleUpdate: (details) { + final newFontSize = _baseFontSize * details.scale; + resize(newFontSize); + }, +) +``` + +## Schéma de couleurs + +- **Clair (Light)** : Fond clair, texte sombre +- **Sombre (Dark)** : Fond sombre, texte clair +- **AMOLED** : Fond noir pur + +## Optimisations de performance + +- **Dirty rectangle** : Ne redessiner que les régions modifiées +- **Mise en cache des lignes** : Mettre en cache les lignes rendues +- **Défilement paresseux (Lazy scrolling)** : Défilement virtuel pour les longs tampons +- **Mises à jour par lots** : Fusionner plusieurs écritures +- **Compression** : Compresser le tampon de défilement +- **Anti-rebond (Debouncing)** : Anti-rebond pour les saisies rapides diff --git a/docs/src/content/docs/fr/quick-start.mdx b/docs/src/content/docs/fr/quick-start.mdx new file mode 100644 index 00000000..953cf955 --- /dev/null +++ b/docs/src/content/docs/fr/quick-start.mdx @@ -0,0 +1,45 @@ +--- +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) diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index c0b7cbd7..dd90afa4 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -1,5 +1,5 @@ --- -title: Flutter Server Box +title: Server Box description: A comprehensive cross-platform server management application hero: tagline: Manage your Linux servers from anywhere @@ -41,6 +41,6 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; ## Quick Links -- **Download**: Available on [App Store](https://apps.apple.com/app/flutter-server-box), [Google Play](https://play.google.com/store/apps/details), [GitHub](https://github.com/lollipopkit/flutter_server_box/releases), and [F-Droid](https://f-droid.org/) -- **Documentation**: Explore the guides to get started with Flutter Server Box +- **Download**: Available on [App Store](https://apps.apple.com/app/id1586449703), [GitHub](https://github.com/lollipopkit/flutter_server_box/releases), and [F-Droid](https://f-droid.org/) +- **Documentation**: Explore the guides to get started with Server Box - **Support**: Join our community on GitHub for discussions and issues diff --git a/docs/src/content/docs/installation.mdx b/docs/src/content/docs/installation.mdx index 5fa6944c..22c073e3 100644 --- a/docs/src/content/docs/installation.mdx +++ b/docs/src/content/docs/installation.mdx @@ -1,21 +1,20 @@ --- title: Installation -description: Download and install Flutter Server Box on your device +description: Download and install Server Box on your device --- -Flutter Server Box is available on multiple platforms. Choose your preferred method of installation. +Server Box is available on multiple platforms. Choose your preferred method of installation. ## Mobile Apps ### iOS -Download from the **[App Store](https://apps.apple.com/app/flutter-server-box)**. +Download from the **[App Store](https://apps.apple.com/app/id1586449703)**. ### Android Choose your preferred source: -- **[Google Play](https://play.google.com/store/apps/details)** - Recommended for most users - **[F-Droid](https://f-droid.org/)** - For users who prefer FOSS-only sources - **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** - For the latest version directly from the source @@ -41,11 +40,11 @@ Download from **[GitHub Releases](https://github.com/lollipopkit/flutter_server_ ## watchOS -Available on the **[App Store](https://apps.apple.com/app/flutter-server-box)** as part of the iOS app. +Available on the **[App Store](https://apps.apple.com/app/id1586449703)** as part of the iOS app. ## Building from Source -To build Flutter Server Box from source, see the [Building](/development/building) section in the Development documentation. +To build Server Box from source, see the [Building](/development/building) section in the Development documentation. ## Version Information diff --git a/docs/src/content/docs/introduction.mdx b/docs/src/content/docs/introduction.mdx index 44325620..6094f40e 100644 --- a/docs/src/content/docs/introduction.mdx +++ b/docs/src/content/docs/introduction.mdx @@ -1,13 +1,13 @@ --- title: Introduction -description: Learn what Flutter Server Box is and what it can do +description: Learn what Server Box is and what it can do --- -Flutter Server Box is a comprehensive cross-platform server management application built with Flutter. It allows you to monitor, manage, and control your Linux, Unix, and Windows servers from anywhere. +Server Box is a comprehensive cross-platform server management application built with Flutter. It allows you to monitor, manage, and control your Linux, Unix, and Windows servers from anywhere. -## What is Flutter Server Box? +## What is Server Box? -Flutter Server Box provides a unified interface for server administration tasks through SSH connections. Whether you're a system administrator, developer, or hobbyist running home servers, this app puts powerful server management tools in your pocket. +Server Box provides a unified interface for server administration tasks through SSH connections. Whether you're a system administrator, developer, or hobbyist running home servers, this app puts powerful server management tools in your pocket. ## Key Capabilities @@ -22,11 +22,10 @@ Flutter Server Box provides a unified interface for server administration tasks ## Supported Platforms -Flutter Server Box is truly cross-platform: +Server Box is truly cross-platform: - **Mobile**: iOS and Android - **Desktop**: macOS, Linux, and Windows -- **Wearable**: watchOS (Apple Watch) ## License diff --git a/docs/src/content/docs/ja/advanced/bulk-import.md b/docs/src/content/docs/ja/advanced/bulk-import.md new file mode 100644 index 00000000..4a49c268 --- /dev/null +++ b/docs/src/content/docs/ja/advanced/bulk-import.md @@ -0,0 +1,83 @@ +--- +title: サーバーの一括インポート +description: JSON ファイルから複数のサーバーをインポートする +--- + +JSON ファイルを使用して、複数のサーバー設定を一度にインポートします。 + +## JSON 形式 + +:::danger[セキュリティ警告] +**プレーンテキストのパスワードをファイルに保存しないでください!** この JSON の例ではデモンストレーションのためにパスワードフィールドを表示していますが、以下の点に注意してください。 + +- **SSH キーを優先** (`keyId`) し、`pwd` の使用は避けてください。その方が安全です。 +- パスワードを使用する必要がある場合は、**シークレットマネージャー**や環境変数を使用してください。 +- インポート後は**直ちにファイルを削除**してください。資格情報を放置しないでください。 +- **.gitignore に追加**してください。資格情報ファイルをバージョン管理にコミットしないでください。 +::: + +```json +[ + { + "name": "My Server", + "ip": "example.com", + "port": 22, + "user": "root", + "pwd": "password", + "keyId": "", + "tags": ["production"], + "autoConnect": false + } +] +``` + +## フィールド + +| フィールド | 必須 | 説明 | +|-------|----------|-------------| +| `name` | はい | 表示名 | +| `ip` | はい | ドメインまたは IP アドレス | +| `port` | はい | SSH ポート (通常は 22) | +| `user` | はい | SSH ユーザー名 | +| `pwd` | いいえ | パスワード (非推奨 - 代わりに SSH キーを使用してください) | +| `keyId` | いいえ | SSH キー名 (「非公開鍵」から取得 - 推奨) | +| `tags` | いいえ | 整理用タグ | +| `autoConnect` | いいえ | 起動時に自動接続 | + +## インポートの手順 + +1. サーバー設定を含む JSON ファイルを作成する +2. 設定 → バックアップ → サーバーの一括インポート +3. JSON ファイルを選択する +4. インポートを確認する + +## 例 + +```json +[ + { + "name": "Production", + "ip": "prod.example.com", + "port": 22, + "user": "admin", + "keyId": "my-key", + "tags": ["production", "web"] + }, + { + "name": "Development", + "ip": "dev.example.com", + "port": 2222, + "user": "dev", + "keyId": "dev-key", + "tags": ["development"] + } +] +``` + +## ヒント + +- 可能な限りパスワードの代わりに **SSH キーを使用**してください。 +- インポート後に**接続をテスト**してください。 +- 管理を容易にするために**タグで整理**してください。 +- インポート後に **JSON ファイルを削除**してください。 +- 資格情報を含む JSON ファイルを**決してコミットしないでください**。 diff --git a/docs/src/content/docs/ja/advanced/custom-commands.md b/docs/src/content/docs/ja/advanced/custom-commands.md new file mode 100644 index 00000000..852a40a4 --- /dev/null +++ b/docs/src/content/docs/ja/advanced/custom-commands.md @@ -0,0 +1,72 @@ +--- +title: カスタムコマンド +description: サーバーページにカスタムコマンドの出力を表示する +--- + +カスタムシェルコマンドを追加して、サーバー詳細ページに出力を表示します。 + +## 設定 + +1. サーバー設定 → カスタムコマンド +2. JSON 形式でコマンドを入力 + +## 基本形式 + +```json +{ + "表示名": "シェルコマンド" +} +``` + +**例:** +```json +{ + "メモリ": "free -h", + "ディスク": "df -h", + "稼働時間": "uptime" +} +``` + +## 結果の確認 + +設定後、カスタムコマンドがサーバー詳細ページに表示され、自動的に更新されます。 + +## 特殊なコマンド名 + +### server_card_top_right + +ホーム画面のサーバーカード(右上の隅)に表示されます。 + +```json +{ + "server_card_top_right": "実行したいコマンド" +} +``` + +## ヒント + +**絶対パスを使用する:** +```json +{"マイ・スクリプト": "/usr/local/bin/my-script.sh"} +``` + +**パイプを使用する:** +```json +{"トッププロセス": "ps aux | sort -rk 3 | head -5"} +``` + +**出力を整形する:** +```json +{"CPU負荷": "uptime | awk -F'load average:' '{print $2}'"} +``` + +**コマンドを高速に保つ:** 最高の体験のために 5 秒以内に完了するようにしてください。 + +**出力を制限する:** +```json +{"ログ": "tail -20 /var/log/syslog"} +``` + +## セキュリティ + +コマンドは SSH ユーザーの権限で実行されます。システムの整合性を変更するようなコマンドは避けてください。 diff --git a/docs/src/content/docs/ja/advanced/custom-logo.md b/docs/src/content/docs/ja/advanced/custom-logo.md new file mode 100644 index 00000000..adeec61e --- /dev/null +++ b/docs/src/content/docs/ja/advanced/custom-logo.md @@ -0,0 +1,54 @@ +--- +title: カスタムサーバーロゴ +description: サーバーカードにカスタム画像を使用する +--- + +画像の URL を使用して、サーバーカードにカスタムロゴを表示します。 + +## 設定 + +1. サーバー設定 → カスタムロゴ +2. 画像の URL を入力 + +## URL プレースホルダー + +### {DIST} - Linux ディストリビューション + +検出されたディストリビューションに自動的に置換されます。 + +``` +https://example.com/{DIST}.png +``` + +例: `debian.png`、`ubuntu.png`、`arch.png` など。 + +### {BRIGHT} - テーマ + +現在のテーマに自動的に置換されます。 + +``` +https://example.com/{BRIGHT}.png +``` + +例: `light.png` または `dark.png`。 + +### 両方を組み合わせる + +``` +https://example.com/{DIST}-{BRIGHT}.png +``` + +例: `debian-light.png`、`ubuntu-dark.png` など。 + +## ヒント + +- PNG または SVG 形式を使用してください。 +- 推奨サイズ: 64x64 〜 128x128 ピクセル。 +- HTTPS の URL を使用してください。 +- ファイルサイズは小さく保ってください。 + +## サポートされているディストリビューション + +debian, ubuntu, centos, fedora, opensuse, kali, alpine, arch, rocky, deepin, armbian, wrt + +全リスト: [`dist.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/model/server/dist.dart) diff --git a/docs/src/content/docs/ja/advanced/json-settings.md b/docs/src/content/docs/ja/advanced/json-settings.md new file mode 100644 index 00000000..f7d38c23 --- /dev/null +++ b/docs/src/content/docs/ja/advanced/json-settings.md @@ -0,0 +1,64 @@ +--- +title: 隠し設定 (JSON) +description: JSON エディタ経由で詳細設定にアクセスする +--- + +一部の設定は UI 上では非表示になっていますが、JSON エディタからアクセス可能です。 + +## アクセス方法 + +サイドメニューの**「設定」**を長押しすると、JSON エディタが開きます。 + +## よく使われる隠し設定 + +### timeOut + +接続のタイムアウト時間(秒)。 + +```json +{"timeOut": 10} +``` + +**型:** integer | **デフォルト:** 5 | **範囲:** 1-60 + +### recordHistory + +履歴(SFTP パスなど)を保存します。 + +```json +{"recordHistory": true} +``` + +**型:** boolean | **デフォルト:** true + +### textFactor + +テキストの倍率。 + +```json +{"textFactor": 1.2} +``` + +**型:** double | **デフォルト:** 1.0 | **範囲:** 0.8-1.5 + +## その他の設定を探す + +すべての設定は [`setting.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/store/setting.dart) で定義されています。 + +以下の形式を探してください。 +```dart +late final settingName = StoreProperty(box, 'settingKey', defaultValue); +``` + +## ⚠️ 重要 + +**編集する前に:** +- **バックアップを作成する** - 設定を間違えるとアプリが起動しなくなる可能性があります。 +- **慎重に編集する** - JSON は有効な形式である必要があります。 + +## 復旧方法 + +編集後にアプリが起動しなくなった場合: +1. アプリのデータを消去する(最終手段) +2. アプリを再インストールする +3. バックアップから復元する diff --git a/docs/src/content/docs/ja/advanced/troubleshooting.md b/docs/src/content/docs/ja/advanced/troubleshooting.md new file mode 100644 index 00000000..d1f3b66b --- /dev/null +++ b/docs/src/content/docs/ja/advanced/troubleshooting.md @@ -0,0 +1,118 @@ +--- +title: 一般的な問題 +description: よくある問題の解決策 +--- + +## 接続の問題 + +### SSH が接続できない + +**症状:** タイムアウト、接続拒否、認証失敗 + +**解決策:** + +1. **サーバーの種類を確認する:** Unix 系のシステムのみがサポートされています (Linux, macOS, Android/Termux)。 +2. **手動でテストする:** `ssh user@server -p port` +3. **ファイアウォールを確認する:** ポート 22 が開放されている必要があります。 +4. **資格情報を確認する:** ユーザー名とパスワード/キーが正しいことを確認してください。 + +### 頻繁に切断される + +**症状:** 放置しているとターミナルが切断される + +**解決策:** + +1. **サーバーの Keep-alive 設定:** + ```bash + # /etc/ssh/sshd_config + ClientAliveInterval 60 + ClientAliveCountMax 3 + ``` + +2. **バッテリーの最適化を無効にする:** + - MIUI: バッテリー → 「制限なし」 + - Android: 設定 → アプリ → 最適化を無効化 + - iOS: バックグラウンド更新を有効にする + +## 入力の問題 + +### 特定の文字が入力できない + +**解決策:** 設定 → キーボードの種類 → `visiblePassword` に切り替える + +注意: この変更後、CJK(中日韓)入力が動作しなくなる可能性があります。 + +## アプリの問題 + +### 起動時にアプリがクラッシュする + +**症状:** アプリが開かない、画面が真っ暗になる + +**原因:** 設定の破損(特に JSON エディタによる編集後) + +**解決策:** + +1. **アプリのデータを消去する:** + - Android: 設定 → アプリ → ServerBox → データを消去 + - iOS: アプリを削除して再インストール + +2. **バックアップから復元する:** 設定を変更する前に作成したバックアップをインポートしてください。 + +### バックアップ/復元の問題 + +**バックアップが動作しない:** +- ストレージの空き容量を確認してください。 +- アプリにストレージ権限があるか確認してください。 +- 別の保存場所を試してください。 + +**復元に失敗する:** +- バックアップファイルの整合性を確認してください。 +- アプリのバージョンの互換性を確認してください。 + +## ウィジェットの問題 + +### ウィジェットが更新されない + +**iOS:** +- 自動更新まで最大 30 分待機してください。 +- ウィジェットを一度削除してから再度追加してください。 +- URL が `/status` で終わっているか確認してください。 + +**Android:** +- ウィジェットをタップして強制的に更新してください。 +- ウィジェット ID がアプリ設定の構成と一致しているか確認してください。 + +**watchOS:** +- ウォッチアプリを再起動してください。 +- 設定変更後、数分待機してください。 +- URL の形式を確認してください。 + +### ウィジェットにエラーが表示される + +- サーバーで ServerBox Monitor が実行されているか確認してください。 +- ブラウザで URL をテストしてください。 +- 認証資格情報を確認してください。 + +## パフォーマンスの問題 + +### アプリが重い + +**解決策:** +- 設定で更新頻度を下げてください。 +- ネットワーク速度を確認してください。 +- 使用していないサーバーを無効にしてください。 + +### バッテリー消費が激しい + +**解決策:** +- 更新間隔を長くしてください。 +- バックグラウンド更新を無効にしてください。 +- 使用していない SSH セッションを閉じてください。 + +## サポートを受ける + +問題が解決しない場合: + +1. **GitHub Issues を検索する:** https://github.com/lollipopkit/flutter_server_box/issues +2. **新しい Issue を作成する:** アプリのバージョン、プラットフォーム、再現手順を含めてください。 +3. **Wiki を確認する:** 本ドキュメントおよび GitHub Wiki を参照してください。 diff --git a/docs/src/content/docs/ja/advanced/widgets.md b/docs/src/content/docs/ja/advanced/widgets.md new file mode 100644 index 00000000..f47c19d8 --- /dev/null +++ b/docs/src/content/docs/ja/advanced/widgets.md @@ -0,0 +1,90 @@ +--- +title: ホーム画面ウィジェット +description: サーバーの状態を表示するウィジェットをホーム画面に追加する +--- + +サーバーに [ServerBox Monitor](https://github.com/lollipopkit/server_box_monitor) がインストールされている必要があります。 + +## 前提条件 + +まずサーバーに ServerBox Monitor をインストールしてください。設定手順については [ServerBox Monitor Wiki](https://github.com/lollipopkit/server_box_monitor/wiki/Home) を参照してください。 + +インストール後、サーバーで以下が利用可能である必要があります。 +- HTTP/HTTPS エンドポイント +- `/status` API エンドポイント +- オプションの認証 + +## URL 形式 + +``` +https://your-server.com/status +``` + +末尾は必ず `/status` である必要があります。 + +## iOS ウィジェット + +### 設定 + +1. ホーム画面を長押し → **+** をタップ +2. 「ServerBox」を検索 +3. ウィジェットのサイズを選択 +4. ウィジェットを長押し → **ウィジェットを編集** +5. `/status` で終わる URL を入力 + +### 注意事項 + +- HTTPS を使用する必要があります(ローカル IP を除く)。 +- 最大更新頻度: 30 分(iOS の制限)。 +- 複数のサーバーに対して複数のウィジェットを追加できます。 + +## Android ウィジェット + +### 設定 + +1. ホーム画面を長押し → **ウィジェット** +2. 「ServerBox」を見つける → ホーム画面に追加 +3. 表示されたウィジェット ID 番号をメモする +4. ServerBox アプリを開く → 設定 +5. **「ホームウィジェットのリンクを構成」**をタップ +6. エントリを追加: `ウィジェット ID` = `ステータス URL` + +例: +- キー: `17` +- 値: `https://my-server.com/status` + +7. ホーム画面のウィジェットをタップして更新 + +## watchOS ウィジェット + +### 設定 + +1. iPhone アプリを開く → 設定 +2. **iOS 設定** → **Watch アプリ** +3. **「URL を追加」**をタップ +4. `/status` で終わる URL を入力 +5. ウォッチアプリが同期されるのを待つ + +### 注意事項 + +- 更新されない場合は、ウォッチアプリの再起動を試してください。 +- iPhone と Apple Watch が接続されていることを確認してください。 + +## トラブルシューティング + +### ウィジェットが更新されない + +**iOS:** 最大 30 分待機し、その後ウィジェットを削除して再追加してください。 +**Android:** ウィジェットをタップして強制的に更新し、設定で ID を確認してください。 +**watchOS:** ウォッチアプリを再起動し、数分待機してください。 + +### ウィジェットにエラーが表示される + +- ServerBox Monitor が実行されているか確認してください。 +- ブラウザで URL をテストしてください。 +- URL が `/status` で終わっているか確認してください。 + +## セキュリティ + +- 可能な限り**常に HTTPS を使用**してください。 +- **ローカル IP** は信頼できるネットワーク内でのみ使用してください。 diff --git a/docs/src/content/docs/ja/development/architecture.md b/docs/src/content/docs/ja/development/architecture.md new file mode 100644 index 00000000..88470872 --- /dev/null +++ b/docs/src/content/docs/ja/development/architecture.md @@ -0,0 +1,86 @@ +--- +title: アーキテクチャ +description: アーキテクチャパターンと設計上の決定事項 +--- + +Server Box は、データ層、ドメイン層、プレゼンテーション層を明確に分離したクリーンアーキテクチャの原則に従っています。 + +## 階層型アーキテクチャ + +``` +┌─────────────────────────────────────┐ +│ プレゼンテーション層 │ +│ (lib/view/page/) │ +│ - ページ、ウィジェット、コントローラー │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ ビジネスロジック層 │ +│ (lib/data/provider/) │ +│ - Riverpod Provider │ +│ - 状態管理 │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ データ層 │ +│ (lib/data/model/, store/) │ +│ - モデル、ストレージ、サービス │ +└─────────────────────────────────────┘ +``` + +## 主要なパターン + +### 状態管理: Riverpod + +- **コード生成**: 型安全な Provider のために `riverpod_generator` を使用 +- **State Notifier**: ビジネスロジックを伴う可変状態に使用 +- **Async Notifier**: ロード中やエラー状態の管理に使用 +- **Stream Provider**: リアルタイムデータに使用 + +### 不変モデル: Freezed + +- すべてのデータモデルで Freezed による不変性を確保 +- 状態表現のための Union 型の活用 +- 組み込みの JSON シリアライズ +- 更新のための CopyWith エクステンション + +### ローカルストレージ: Hive + +- **hive_ce**: Hive のコミュニティ版を使用 +- 手動での `@HiveField` や `@HiveType` の指定が不要 +- 型アダプターの自動生成 +- 永続的なキーバリューストレージ + +## 依存関係の注入 (DI) + +サービスやストアは以下を通じて注入されます。 + +1. **Provider**: UI に対して依存関係を公開 +2. **GetIt**: サービスロケーター(必要に応じて使用) +3. **コンストラクタ注入**: 明示的な依存関係の定義 + +## データフロー + +``` +ユーザーアクション → Widget → Provider → サービス/ストア → モデル更新 → UI 再構築 +``` + +1. ユーザーがウィジェットを操作 +2. ウィジェットが Provider のメソッドを呼び出す +3. Provider がサービス/ストアを通じて状態を更新 +4. 状態の変化により UI の再構築がトリガーされる +5. 新しい状態がウィジェットに反映される + +## カスタム依存関係 + +プロジェクトでは、機能を拡張するためにいくつかのカスタムフォークを使用しています。 + +- **dartssh2**: 強化された SSH 機能 +- **xterm**: モバイル対応のターミナルエミュレータ +- **fl_lib**: 共有 UI コンポーネントとユーティリティ + +## スレッディング + +- **Isolate**: 重い計算処理をメインスレッドから分離 +- **computer パッケージ**: マルチスレッド用のユーティリティ +- **Async/Await**: 非ブロッキングな I/O 操作 diff --git a/docs/src/content/docs/ja/development/building.md b/docs/src/content/docs/ja/development/building.md new file mode 100644 index 00000000..a261ffa8 --- /dev/null +++ b/docs/src/content/docs/ja/development/building.md @@ -0,0 +1,116 @@ +--- +title: ビルド +description: 各プラットフォーム向けのビルド手順 +--- + +Server Box は、クロスプラットフォームビルドのためにカスタムビルドシステム (`fl_build`) を使用しています。 + +## 前提条件 + +- Flutter SDK (stable チャネル) +- プラットフォーム固有のツール (iOS 用の Xcode、Android 用の Android Studio) +- Rust ツールチェーン (一部のネイティブ依存関係のため) + +## 開発用ビルド + +```bash +# 開発モードで実行 +flutter run + +# 特定のデバイスで実行 +flutter run -d +``` + +## 製品用ビルド + +プロジェクトではビルドに `fl_build` を使用します。 + +```bash +# 特定のプラットフォーム向けにビルド +dart run fl_build -p + +# 利用可能なプラットフォーム: +# - ios +# - android +# - macos +# - linux +# - windows +``` + +## プラットフォーム固有のビルド + +### iOS + +```bash +dart run fl_build -p ios +``` + +以下が必要です。 +- Xcode がインストールされた macOS +- CocoaPods +- 署名用の Apple Developer アカウント + +### Android + +```bash +dart run fl_build -p android +``` + +以下が必要です。 +- Android SDK +- Java Development Kit (JDK) +- 署名用のキーストア + +### macOS + +```bash +dart run fl_build -p macos +``` + +### Linux + +```bash +dart run fl_build -p linux +``` + +### Windows + +```bash +dart run fl_build -p windows +``` + +Visual Studio がインストールされた Windows が必要です。 + +## ビルド前/後処理 + +`make.dart` スクリプトが以下を処理します。 + +- メタデータの生成 +- バージョン文字列の更新 +- プラットフォーム固有の構成 + +## トラブルシューティング + +### クリーンビルド + +```bash +flutter clean +dart run build_runner build --delete-conflicting-outputs +flutter pub get +``` + +### バージョンの不一致 + +すべての依存関係に互換性があることを確認してください。 +```bash +flutter pub upgrade +``` + +## リリースチェックリスト + +1. `pubspec.yaml` のバージョンを更新する +2. コード生成を実行する +3. テストを実行する +4. すべてのターゲットプラットフォーム向けにビルドする +5. 実機でテストする +6. GitHub リリースを作成する diff --git a/docs/src/content/docs/ja/development/codegen.md b/docs/src/content/docs/ja/development/codegen.md new file mode 100644 index 00000000..5dc6d126 --- /dev/null +++ b/docs/src/content/docs/ja/development/codegen.md @@ -0,0 +1,98 @@ +--- +title: コード生成 +description: build_runner を使用したコード生成 +--- + +Server Box では、モデル、状態管理、シリアライズのためにコード生成を多用しています。 + +## コード生成を実行するタイミング + +以下のファイルを変更した後に実行してください。 + +- `@freezed` アノテーションが付いたモデル +- `@JsonSerializable` が付いたクラス +- Hive モデル +- `@riverpod` が付いた Provider +- ローカライズファイル (ARB ファイル) + +## コード生成の実行 + +```bash +# すべてのコードを生成 +dart run build_runner build --delete-conflicting-outputs + +# クリーンアップして再生成 +dart run build_runner build --delete-conflicting-outputs --clean +``` + +## 生成されるファイル + +### Freezed (`*.freezed.dart`) + +Union 型を持つ不変データモデル: + +```dart +@freezed +class ServerState with _$ServerState { + const factory ServerState.connected() = Connected; + const factory ServerState.disconnected() = Disconnected; + const factory ServerState.error(String message) = Error; +} +``` + +### JSON シリアライズ (`*.g.dart`) + +`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 json) => + _$ServerFromJson(json); + Map toJson() => _$ServerToJson(this); +} +``` + +### Riverpod Provider (`*.g.dart`) + +`@riverpod` アノテーションから生成されます: + +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; +} +``` + +### Hive アダプター (`*.g.dart`) + +Hive モデル (hive_ce) 用に自動生成されます: + +```dart +@HiveType(typeId: 0) +class ServerModel { + @HiveField(0) + final String id; +} +``` + +## ローカライズの生成 + +```bash +flutter gen-l10n +``` + +`lib/l10n/*.arb` ファイルから `lib/generated/l10n/` を生成します。 + +## ヒント + +- 競合を避けるために `--delete-conflicting-outputs` を使用してください。 +- 生成されたファイルを `.gitignore` に追加してください。 +- 生成されたファイルを手動で編集しないでください。 diff --git a/docs/src/content/docs/ja/development/state.md b/docs/src/content/docs/ja/development/state.md new file mode 100644 index 00000000..36d1c876 --- /dev/null +++ b/docs/src/content/docs/ja/development/state.md @@ -0,0 +1,115 @@ +--- +title: 状態管理 +description: Riverpod ベースの状態管理パターン +--- + +Server Box は、状態管理のためにコード生成を伴う Riverpod を使用しています。 + +## Provider の種類 + +### StateProvider + +読み書き可能な単純な状態: + +```dart +@riverpod +class Settings extends _$Settings { + @override + SettingsModel build() { + return SettingsModel.defaults(); + } + + void update(SettingsModel newSettings) { + state = newSettings; + } +} +``` + +### AsyncNotifierProvider + +ロード中やエラー状態を伴う、非同期にロードされる状態: + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + return fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => fetchStatus(server)); + } +} +``` + +### StreamProvider + +ストリームからのリアルタイムデータ: + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + return cpuService.monitor(server); +} +``` + +## 状態パターン + +### ロード状態 + +```dart +state.when( + data: (data) => DataWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### Family Provider + +パラメータを受け取る Provider: + +```dart +@riverpod +List containers(ContainersRef ref, Server server) { + return containerService.list(server); +} +``` + +### Auto-Dispose + +参照されなくなったときに破棄される Provider: + +```dart +@Riverpod(keepAlive: false) +class TempState extends _$TempState { + // ... +} +``` + +## ベストプラクティス + +1. **コード生成を使用する**: 常に `@riverpod` アノテーションを使用してください +2. **Provider を近くに配置する**: 利用するウィジェットの近くに定義してください +3. **シングルトンを避ける**: 代わりに Provider を使用してください +4. **適切に階層化する**: UI ロジックとビジネスロジックを分離してください + +## ウィジェットでの状態の読み取り + +```dart +class ServerWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final status = ref.watch(serverStatusProvider(server)); + return status.when(...); + } +} +``` + +## 状態の変更 + +```dart +ref.read(settingsProvider.notifier).update(newSettings); +``` diff --git a/docs/src/content/docs/ja/development/structure.md b/docs/src/content/docs/ja/development/structure.md new file mode 100644 index 00000000..9e7ba46c --- /dev/null +++ b/docs/src/content/docs/ja/development/structure.md @@ -0,0 +1,96 @@ +--- +title: プロジェクト構造 +description: Server Box のコードベースを理解する +--- + +Server Box プロジェクトは、関心の分離を明確にしたモジュール式のアーキテクチャを採用しています。 + +## ディレクトリ構造 + +``` +lib/ +├── core/ # コアユーティリティとエクステンション +├── data/ # データ層 +│ ├── model/ # 機能別のデータモデル +│ ├── provider/ # Riverpod Provider +│ └── store/ # ローカルストレージ (Hive) +├── view/ # UI 層 +│ ├── page/ # 主要なページ +│ └── widget/ # 再利用可能なウィジェット +├── generated/ # 生成されたローカライズファイル +├── l10n/ # ローカライズ用 ARB ファイル +└── hive/ # Hive アダプター +``` + +## コア層 (`lib/core/`) + +ユーティリティ、エクステンション、およびルーティング構成が含まれます。 + +- **Extensions**: 一般的な型に対する Dart のエクステンション +- **Routes**: アプリのルーティング構成 +- **Utils**: 共有ユーティリティ関数 + +## データ層 (`lib/data/`) + +### モデル (`lib/data/model/`) + +機能ごとに整理されています。 + +- `server/` - サーバー接続およびステータスモデル +- `container/` - Docker コンテナモデル +- `ssh/` - SSH セッションモデル +- `sftp/` - SFTP ファイルモデル +- `app/` - アプリ固有のモデル + +### Provider (`lib/data/provider/`) + +依存関係の注入と状態管理のための Riverpod Provider。 + +- サーバー関連の Provider +- UI 状態の Provider +- サービス関連の Provider + +### ストア (`lib/data/store/`) + +Hive ベースのローカルストレージ。 + +- サーバー情報の保存 +- 設定情報の保存 +- キャッシュ情報の保存 + +## UI 層 (`lib/view/`) + +### ページ (`lib/view/page/`) + +アプリケーションのメイン画面。 + +- `server/` - サーバー管理ページ +- `ssh/` - SSH ターミナルページ +- `container/` - コンテナ管理ページ +- `setting/` - 設定ページ +- `storage/` - SFTP ページ +- `snippet/` - スニペットページ + +### ウィジェット (`lib/view/widget/`) + +再利用可能な UI コンポーネント。 + +- サーバーカード +- ステータスチャート +- 入力コンポーネント +- ダイアログ + +## 生成されたファイル + +- `lib/generated/l10n/` - 自動生成されたローカライズコード +- `*.g.dart` - 生成されたコード (json_serializable, freezed, hive, riverpod) +- `*.freezed.dart` - Freezed による不変クラス + +## パッケージディレクトリ (`/packages/`) + +依存関係のカスタムフォークが含まれています。 + +- `dartssh2/` - SSH ライブラリ +- `xterm/` - ターミナルエミュレータ +- `fl_lib/` - 共有ユーティリティ +- `fl_build/` - ビルドシステム diff --git a/docs/src/content/docs/ja/development/testing.md b/docs/src/content/docs/ja/development/testing.md new file mode 100644 index 00000000..481e708a --- /dev/null +++ b/docs/src/content/docs/ja/development/testing.md @@ -0,0 +1,113 @@ +--- +title: テスト +description: テスト戦略とテストの実行 +--- + +## テストの実行 + +```bash +# すべてのテストを実行 +flutter test + +# 特定のテストファイルを実行 +flutter test test/battery_test.dart + +# カバレッジ付きで実行 +flutter test --coverage +``` + +## テスト構造 + +テストは `test/` ディレクトリにあり、lib の構造を反映しています: + +``` +test/ +├── data/ +│ ├── model/ +│ └── provider/ +├── view/ +│ └── widget/ +└── test_helpers.dart +``` + +## ユニットテスト + +ビジネスロジックとデータモデルのテスト: + +```dart +test('CPU使用率を計算する必要があります', () { + final cpu = CpuModel(usage: 75.0); + expect(cpu.usagePercentage, '75%'); +}); +``` + +## ウィジェットテスト + +UI コンポーネントのテスト: + +```dart +testWidgets('ServerCard にサーバー名が表示されること', (tester) async { + await tester.pumpWidget( + ProviderScope( + child: MaterialApp( + home: ServerCard(server: testServer), + ), + ), + ); + + expect(find.text('Test Server'), findsOneWidget); +}); +``` + +## プロバイダーテスト + +Riverpod プロバイダーのテスト: + +```dart +test('serverStatusProvider がステータスを返すこと', () async { + final container = ProviderContainer(); + final status = await container.read(serverStatusProvider(testServer).future); + expect(status, isA()); +}); +``` + +## モック (Mocking) + +外部依存関係にモックを使用する: + +```dart +class MockSshService extends Mock implements SshService {} + +test('サーバーに接続すること', () async { + final mockSsh = MockSshService(); + when(mockSsh.connect(any)).thenAnswer((_) async => true); + + // モックを使用してテスト +}); +``` + +## 統合テスト + +完全なユーザーフローのテスト (`integration_test/` 内): + +```dart +testWidgets('サーバー追加フロー', (tester) async { + await tester.pumpWidget(MyApp()); + + // 追加ボタンをタップ + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + + // フォームを入力 + await tester.enterText(find.byKey(Key('name')), 'Test Server'); + // ... +}); +``` + +## ベストプラクティス + +1. **Arrange-Act-Assert**: テストを明確に構造化する +2. **記述的な名前**: テスト名は動作を記述する必要がある +3. **1つのテストに1つのアサーション**: テストの焦点を絞る +4. **外部依存関係をモックする**: 実際のサーバーに依存しない +5. **エッジケースのテスト**: 空のリスト、null 値など diff --git a/docs/src/content/docs/ja/index.mdx b/docs/src/content/docs/ja/index.mdx new file mode 100644 index 00000000..dd43e86b --- /dev/null +++ b/docs/src/content/docs/ja/index.mdx @@ -0,0 +1,46 @@ +--- +title: Server Box +description: 総合的なクロスプラットフォーム・サーバー管理アプリケーション +hero: + tagline: どこからでも Linux サーバーを管理 + actions: + - text: はじめる + link: /ja/introduction/ + icon: right-arrow + variant: primary + - text: GitHub で見る + link: https://github.com/lollipopkit/flutter_server_box + icon: github + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +## 特徴 + + + + CPU、メモリ、ディスク、ネットワーク、GPU、温度を美しいリアルタイムチャートで監視します。 + + + マルチタブ対応、モバイルデバイス用仮想キーボードを備えたフル機能の SSH ターミナル。 + + + 内蔵の SFTP クライアントとローカルファイルブラウザでサーバー上のファイルを管理。 + + + 直感的なインターフェースで Docker コンテナの起動、停止、監視が可能。 + + + iOS、Android、macOS、Linux、Windows、watchOS で利用可能。 + + + 英語、中国語、ドイツ語、フランス語など、フルローカライズをサポート。 + + + +## クイックリンク + +- **ダウンロード**: [App Store](https://apps.apple.com/app/id1586449703)、[GitHub](https://github.com/lollipopkit/flutter_server_box/releases)、[F-Droid](https://f-droid.org/) で入手可能 +- **ドキュメント**: Server Box を使い始めるためのガイドを確認 +- **サポート**: GitHub コミュニティに参加して議論や問題報告を行う diff --git a/docs/src/content/docs/ja/installation.mdx b/docs/src/content/docs/ja/installation.mdx new file mode 100644 index 00000000..732a28aa --- /dev/null +++ b/docs/src/content/docs/ja/installation.mdx @@ -0,0 +1,51 @@ +--- +title: インストール +description: お使いのデバイスに Server Box をダウンロードしてインストールする +--- + +Server Box は複数のプラットフォームで利用可能です。お好みのインストール方法を選択してください。 + +## モバイルアプリ + +### iOS + +**[App Store](https://apps.apple.com/app/id1586449703)** からダウンロードしてください。 + +### Android + +お好みのソースを選択してください: + +- **[F-Droid](https://f-droid.org/)** - FOSS(自由でオープンソースのソフトウェア)のみのソースを好むユーザー向け +- **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** - ソースから直接最新バージョンを入手したいユーザー向け + +## デスクトップアプリ + +### macOS + +**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** からダウンロードしてください。 + +特徴: +- ネイティブメニューバーへの統合 +- Intel と Apple Silicon の両方をサポート + +### Linux + +**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** からダウンロードしてください。 + +AppImage、deb、または tar.gz パッケージとして利用可能です。 + +### Windows + +**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** からダウンロードしてください。 + +## watchOS + +iOS アプリの一部として **[App Store](https://apps.apple.com/app/id1586449703)** で利用可能です。 + +## ソースからのビルド + +Server Box をソースからビルドするには、開発ドキュメントの [ビルド](/ja/development/building/) セクションを参照してください。 + +## バージョン情報 + +最新バージョンと変更履歴については、[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases) ページを確認してください。 diff --git a/docs/src/content/docs/ja/introduction.mdx b/docs/src/content/docs/ja/introduction.mdx new file mode 100644 index 00000000..0af8a977 --- /dev/null +++ b/docs/src/content/docs/ja/introduction.mdx @@ -0,0 +1,32 @@ +--- +title: はじめに +description: Server Box とはその何か、何ができるのかを学ぶ +--- + +Server Box は、Flutter で構築された包括的なクロスプラットフォームのサーバー管理アプリケーションです。Linux、Unix、および Windows サーバーをどこからでも監視、管理、および制御できます。 + +## Server Box とは? + +Server Box は、SSH 接続を介してサーバー管理タスクのための統一されたインターフェースを提供します。システム管理者、開発者、またはホームサーバーを運用している愛好家であっても、このアプリは強力なサーバー管理ツールをあなたのポケットに届けます。 + +## 主な機能 + +- **リアルタイム監視**: CPU、メモリ、ディスク使用量、ネットワーク速度、GPU ステータス、およびシステム温度を追跡 +- **SSH ターミナル**: マルチタブをサポートし、外観をカスタマイズ可能なフルターミナルアクセス +- **SFTP クライアント**: サーバー上のファイルを閲覧および管理 +- **Docker 管理**: コンテナを簡単に制御 +- **プロセス管理**: システムプロセスを表示および管理 +- **Systemd サービス**: systemd サービスの開始、停止、および監視 +- **ネットワークツール**: iPerf テスト、ping、および Wake-on-LAN +- **スニペット**: カスタムシェルコマンドを保存して実行 + +## サポートされているプラットフォーム + +Server Box は真のクロスプラットフォームです: + +- **モバイル**: iOS および Android +- **デスクトップ**: macOS、Linux、および Windows + +## ライセンス + +このプロジェクトは AGPL v3 ライセンスの下で提供されています。ソースコードは [GitHub](https://github.com/lollipopkit/flutter_server_box) で公開されています。 diff --git a/docs/src/content/docs/ja/platforms/desktop.md b/docs/src/content/docs/ja/platforms/desktop.md new file mode 100644 index 00000000..10e9d055 --- /dev/null +++ b/docs/src/content/docs/ja/platforms/desktop.md @@ -0,0 +1,78 @@ +--- +title: デスクトップ版の機能 +description: macOS、Linux、Windows 特有の機能 +--- + +デスクトッププラットフォーム上の Server Box は、さらなる生産性向上機能を提供します。 + +## macOS + +### メニューバーへの統合 + +- メニューバーでサーバーの状態を素早く確認 +- ワンクリックでサーバーにアクセス +- 集中を妨げないコンパクトモード +- ネイティブな macOS メニューバースタイル + +### ウィンドウ状態の保持 + +- ウィンドウの位置とサイズを記憶 +- 起動時に前回のセッションを復元 +- マルチモニター対応 + +### ネイティブ機能 + +- **タイトルバー**: カスタムまたはシステムタイトルバーの選択オプション +- **フルスクリーンモード**: 専用のサーバー監視ビュー +- **キーボードショートカット**: macOS ネイティブなショートカット +- **Touch Bar**(対応デバイス): クイックアクション + +## Linux + +### ネイティブ統合 + +- システムトレイのサポート +- デスクトップ通知の統合 +- ファイルピッカーの統合 + +### ウィンドウ管理 + +- X11 および Wayland のサポート +- タイル型ウィンドウマネージャーに最適化 +- カスタムウィンドウ装飾オプション + +## Windows + +### 機能 + +- システムトレイへの統合 +- ジャンプリストによるクイックアクション +- ネイティブなウィンドウコントロール +- 起動時の自動実行オプション + +## クロスプラットフォーム・デスクトップ機能 + +### キーボードショートカット + +**macOS のみ:** +- **Cmd + ,**: 設定 +- **Cmd + Q**: アプリを終了 +- **Cmd + 1-9**: タブ切り替え + +:::note +Windows と Linux では、ネイティブなメニューバーとキーボードショートカットは現在実装されていません。 +::: + +### テーマ + +- ライトテーマ +- ダークテーマ +- AMOLED テーマ(純粋な黒) +- システムテーマ(OS の設定に追従) + +### モバイル版に対する利点 + +- 監視用の広い画面 +- ターミナル操作に最適なフルキーボード +- 高速なファイル操作 +- 優れたマルチタスク性能 diff --git a/docs/src/content/docs/ja/platforms/mobile.md b/docs/src/content/docs/ja/platforms/mobile.md new file mode 100644 index 00000000..8b1bd0d2 --- /dev/null +++ b/docs/src/content/docs/ja/platforms/mobile.md @@ -0,0 +1,77 @@ +--- +title: モバイル版の機能 +description: iOS および Android 特有の機能 +--- + +Server Box は、iOS および Android デバイス向けにいくつかのモバイル特有の機能を提供しています。 + +## 生体認証 + +生体認証を使用してサーバーを保護します。 + +- **iOS**: Face ID または Touch ID +- **Android**: 指紋認証 + +設定 > セキュリティ > 生体認証 から有効にできます。 + +## ホーム画面ウィジェット + +ホーム画面にサーバーの状態を表示するウィジェットを追加して、素早く監視できます。 + +### iOS + +- ホーム画面を長押し +- **+** をタップしてウィジェットを追加 +- 「Server Box」を検索 +- ウィジェットのサイズを選択: + - 小: 単一サーバーの状態 + - 中: 複数サーバーの状態 + - 大: 詳細情報 + +### Android + +- ホーム画面を長押し +- **ウィジェット**をタップ +- 「Server Box」を見つける +- ウィジェットの種類を選択 + +## バックグラウンド実行 + +### Android + +バックグラウンドで接続を維持します。 + +- 設定 > 詳細設定 > バックグラウンド実行 から有効にできます。 +- バッテリーの最適化からの除外が必要です。 +- アクティブな接続については常駐通知が表示されます。 + +### iOS + +バックグラウンド制限が適用されます。 + +- バックグラウンドでは接続が一時停止する場合があります。 +- アプリに戻った際に素早く再接続されます。 +- バックグラウンド更新をサポートしています。 + +## プッシュ通知 + +以下の通知を受け取ることができます。 + +- サーバーのオフライン通知 +- リソース使用率の高騰警告 +- タスク完了通知 + +設定 > 通知 から構成できます。 + +## モバイル UI 機能 + +- **スワイプして更新**: サーバーの状態を更新 +- **スワイプアクション**: サーバーのクイック操作 +- **横画面モード**: ターミナルの操作性を向上 +- **仮想キーボード**: ターミナル用のショートカットキー + +## ファイルの統合 + +- **ファイルアプリ (iOS)**: 「ファイル」アプリから SFTP に直接アクセス +- **Storage Access Framework (Android)**: 他のアプリとファイルを共有 +- **ドキュメントピッカー**: 簡単なファイル選択 diff --git a/docs/src/content/docs/ja/principles/architecture.md b/docs/src/content/docs/ja/principles/architecture.md new file mode 100644 index 00000000..8f2d82bc --- /dev/null +++ b/docs/src/content/docs/ja/principles/architecture.md @@ -0,0 +1,214 @@ +--- +title: アーキテクチャの概要 +description: アプリケーションのハイレベル・アーキテクチャ +--- + +Server Box は、関心の分離を明確にした階層型アーキテクチャを採用しています。 + +## アーキテクチャ階層 + +``` +┌─────────────────────────────────────────────────┐ +│ プレゼンテーション層 (UI) │ +│ lib/view/page/, lib/view/widget/ │ +│ - ページ、ウィジェット、コントローラー │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ ビジネスロジック層 │ +│ lib/data/provider/ │ +│ - Riverpod Provider, State Notifier │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ データアクセス層 │ +│ lib/data/store/, lib/data/model/ │ +│ - Hive Store, データモデル │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ 外部統合層 │ +│ - SSH (dartssh2), ターミナル (xterm), SFTP │ +│ - プラットフォーム固有コード (iOS, Android等) │ +└─────────────────────────────────────────────────┘ +``` + +## アプリケーションの基盤 + +### メインエントリーポイント + +`lib/main.dart` がアプリを初期化します。 + +```dart +void main() { + runApp( + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +### ルートウィジェット + +`MyApp` は以下を提供します。 +- **テーマ管理**: ライト/ダークテーマの切り替え +- **ルーティング構成**: ナビゲーション構造 +- **Provider Scope**: 依存関係注入のルート + +### ホームページ + +`HomePage` はナビゲーションのハブとして機能します。 +- **タブインターフェース**: サーバー、スニペット、コンテナ、SSH +- **状態管理**: タブごとの状態保持 +- **ナビゲーション**: 各機能へのアクセス + +## コアシステム + +### 状態管理: Riverpod + +**なぜ Riverpod なのか?** +- コンパイル時の安全性 +- テストの容易さ +- Build context への依存がない +- プラットフォームを問わず動作する + +**使用されている Provider の種類:** +- `StateProvider`: 単純な可変状態 +- `AsyncNotifierProvider`: ロード中/エラー/データ状態の管理 +- `StreamProvider`: リアルタイムデータストリーム +- Future provider: 一回限りの非同期操作 + +### データ永続化: Hive CE + +**なぜ Hive CE なのか?** +- ネイティブコードへの依存がない +- 高速なキーバリューストレージ +- コード生成による型安全性 +- 手動でのフィールドアノテーションが不要 + +**ストア:** +- `SettingStore`: アプリの設定 +- `ServerStore`: サーバー構成 +- `SnippetStore`: コマンドスニペット +- `KeyStore`: SSH キー + +### 不変モデル: Freezed + +**利点:** +- コンパイル時の不変性 +- 状態管理のための Union 型 +- 組み込みの JSON シリアライズ +- CopyWith エクステンション + +## クロスプラットフォーム戦略 + +### プラグインシステム + +Flutter プラグインが各プラットフォームとの統合を提供します。 + +| プラットフォーム | 統合方法 | +|----------|-------------------| +| iOS | CocoaPods, Swift/Obj-C | +| Android | Gradle, Kotlin/Java | +| macOS | CocoaPods, Swift | +| Linux | CMake, C++ | +| Windows | CMake, C# | + +### プラットフォーム固有機能 + +**iOS のみ:** +- ホーム画面ウィジェット +- ライブアクティビティ +- Apple Watch コンパニオン + +**Android のみ:** +- バックグラウンドサービス +- プッシュ通知 +- ファイルシステムへのアクセス + +**デスクトップのみ:** +- メニューバーへの統合 +- マルチウィンドウ対応 +- カスタムタイトルバー + +## カスタム依存関係 + +### dartssh2 (フォーク版) + +以下の機能を備えた強化版 SSH クライアント: +- モバイルサポートの改善 +- エラーハンドリングの強化 +- パフォーマンスの最適化 + +### xterm.dart (フォーク版) + +以下の機能を備えたターミナルエミュレータ: +- モバイル向けに最適化されたレンダリング +- タッチジェスチャーのサポート +- 仮想キーボードとの統合 + +### fl_lib + +以下の共有ユーティリティパッケージ: +- 共通ウィジェット +- エクステンション +- ヘルパー関数 + +## ビルドシステム + +### fl_build パッケージ + +以下のためのカスタムビルドシステム: +- マルチプラットフォームビルド +- コード署名 +- アセットのバンドル +- バージョン管理 + +### ビルドプロセス + +``` +make.dart (バージョン計算) → fl_build (ビルド実行) → プラットフォーム別出力 +``` + +1. **プリビルド**: Git からバージョンを算出 +2. **ビルド**: ターゲットプラットフォーム向けにコンパイル +3. **ポストビルド**: パッケージングと署名 + +## データフローの例 + +### サーバー状態の更新 + +``` +1. タイマーが発火 → +2. Provider がサービスを呼び出す → +3. サービスが SSH コマンドを実行 → +4. レスポンスをモデルにパース → +5. 状態が更新される → +6. 新しいデータで UI が再構築される +``` + +### ユーザーアクションのフロー + +``` +1. ユーザーがボタンをタップ → +2. ウィジェットが Provider のメソッドを呼び出す → +3. Provider が状態を更新 → +4. 状態の変化により再構築がトリガーされる → +5. 新しい状態が UI に反映される +``` + +## セキュリティアーキテクチャ + +### データ保護 + +- **パスワード**: flutter_secure_storage で暗号化 +- **SSH キー**: 保存時に暗号化 +- **ホストフィンガープリント**: 安全に保存 +- **セッションデータ**: 永続化しない + +### 接続のセキュリティ + +- **ホストキー検証**: 中間者攻撃の検知 +- **暗号化**: 標準的な SSH 暗号化 +- **平文保存の禁止**: 機密データは決して平文で保存しない diff --git a/docs/src/content/docs/ja/principles/sftp.md b/docs/src/content/docs/ja/principles/sftp.md new file mode 100644 index 00000000..3df0b139 --- /dev/null +++ b/docs/src/content/docs/ja/principles/sftp.md @@ -0,0 +1,490 @@ +--- +title: SFTP システム +description: SFTP ファイルブラウザの仕組み +--- + +SFTP システムは、SSH を介したファイル管理機能を提供します。 + +## アーキテクチャ + +``` +┌─────────────────────────────────────────────┐ +│ SFTP UI レイヤー │ +│ - ファイルブラウザ (リモート) │ +│ - ファイルブラウザ (ローカル) │ +│ - 転送キュー │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SFTP 状態管理 │ +│ - sftpProvider │ +│ - パス管理 │ +│ - 操作キュー │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SFTP プロトコルレイヤー │ +│ - SSH サブシステム │ +│ - ファイル操作 │ +│ - ディレクトリ一覧取得 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SSH 転送 │ +│ - セキュアチャネル │ +│ - データストリーミング │ +└─────────────────────────────────────────────┘ +``` + +## 接続の確立 + +### SFTP クライアントの作成 + +```dart +Future createSftpClient(Spi spi) async { + // 1. SSH クライアントを取得 (利用可能な場合は再利用) + final sshClient = await genClient(spi); + + // 2. SFTP サブシステムを開く + final sftp = await sshClient.openSftp(); + + return sftp; +} +``` + +### 接続の再利用 + +SFTP は既存の SSH 接続を再利用します。 + +```dart +class ServerProvider { + SSHClient? _sshClient; + SftpClient? _sftpClient; + + Future getSftpClient(String spiId) async { + _sftpClient ??= await _sshClient!.openSftp(); + return _sftpClient!; + } +} +``` + +## ファイルシステム操作 + +### ディレクトリ一覧取得 + +```dart +Future> listDirectory(String path) async { + final sftp = await getSftpClient(spiId); + + // ディレクトリ一覧を取得 + final files = await sftp.listDir(path); + + // 設定に基づいてソート + 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); + } + }); + + // 設定されている場合はフォルダを先に表示 + if (showFoldersFirst) { + final dirs = files.where((f) => f.isDirectory); + final regular = files.where((f) => !f.isDirectory); + return [...dirs, ...regular]; + } + + return files; +} +``` + +### ファイルメタデータ + +```dart +class SftpFile { + final String name; + final String path; + final int size; // バイト + final int modified; // Unix タイムスタンプ + final String permissions; // 例: "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); +} +``` + +## ファイル操作 + +### アップロード + +```dart +Future uploadFile( + String localPath, + String remotePath, +) async { + final sftp = await getSftpClient(spiId); + + // リクエストを作成 + final req = SftpReq( + spi: spi, + remotePath: remotePath, + localPath: localPath, + type: SftpReqType.upload, + ); + + // キューに追加 + _transferQueue.add(req); + + // プログレス付きで転送を実行 + 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); + }, + ); + + // 完了 + _transferQueue.remove(req); +} +``` + +### ダウンロード + +```dart +Future downloadFile( + String remotePath, + String localPath, +) async { + final sftp = await getSftpClient(spiId); + + // ローカルファイルを作成 + final file = File(localPath); + final sink = file.openWrite(); + + // プログレス付きでダウンロード + final stat = await sftp.stat(remotePath); + + await sftp.download( + fromPath: remotePath, + toSink: sink, + onProgress: (transferred) { + _updateProgress( + SftpReq(...), + transferred, + stat.size, + ); + }, + ); + + await sink.close(); +} +``` + +### 権限の編集 + +```dart +Future setPermissions( + String path, + String permissions, +) async { + final sftp = await getSftpClient(spiId); + + // 権限をパース (例: "rwxr-xr-x" または "755") + final mode = parsePermissions(permissions); + + // SSH コマンド経由で設定 (SFTP より信頼性が高い) + final ssh = await getSshClient(spiId); + await ssh.exec('chmod $mode "$path"'); +} +``` + +## パス管理 + +### パス構造 + +```dart +class PathWithPrefix { + final String prefix; // 例: "/home/user" + final String path; // 相対パスまたは絶対パス + + String get fullPath { + if (path.startsWith('/')) { + return path; // 絶対パス + } + return '$prefix/$path'; // 相対パス + } + + PathWithPrefix cd(String subPath) { + return PathWithPrefix( + prefix: fullPath, + path: subPath, + ); + } +} +``` + +### ナビゲーション履歴 + +```dart +class PathHistory { + final List _history = []; + int _index = -1; + + void push(String path) { + // 前方の履歴を削除 + _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; + } +} +``` + +## 転送システム + +### 転送リクエスト + +```dart +class SftpReq { + final Spi spi; + final String remotePath; + final String localPath; + final SftpReqType type; + final DateTime createdAt; + + int? totalBytes; + int? transferredBytes; + String? error; +} +``` + +### プログレス追跡 + +```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); + } +} +``` + +### キュー管理 + +```dart +class TransferQueue { + final List _queue = []; + final Map _progress = {}; + int _concurrent = 3; // 最大同時転送数 + + Future 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 _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); + } + } +} +``` + +## ローカルストレージパターン + +### ダウンロードキャッシュ + +ダウンロードされたファイルは以下に保存されます。 + +```dart +String getLocalDownloadPath(String spiId, String remotePath) { + final normalized = remotePath.replaceAll('/', '_'); + return 'Paths.file/$spiId/$normalized'; +} +``` + +例: +- リモート: `/var/log/nginx/access.log` +- spiId: `server-123` +- ローカル: `Paths.file/server-123/_var_log_nginx_access.log` + +## ファイル編集 + +### 編集ワークフロー + +```dart +Future editFile(String path) async { + final sftp = await getSftpClient(spiId); + + // 1. サイズチェック + final stat = await sftp.stat(path); + if (stat.size > editorMaxSize) { + showWarning('ファイルが大きすぎるため、内蔵エディタで開けません'); + return; + } + + // 2. 一時ディレクトリにダウンロード + final temp = await downloadToTemp(path); + + // 3. エディタで開く + final content = await openEditor(temp.path); + + // 4. アップロードして戻す + await uploadFile(temp.path, path); + + // 5. クリーンアップ + await temp.delete(); +} +``` + +### 外部エディタとの統合 + +```dart +Future editInExternalEditor(String path) async { + final ssh = await getSshClient(spiId); + + // エディタを使用してターミナルを開く + final editor = getSetting('sftpEditor', 'vim'); + await ssh.exec('$editor "$path"'); + + // ユーザーがターミナルで編集 + // 保存後、SFTP ビューを更新 +} +``` + +## エラーハンドリング + +### 権限エラー + +```dart +try { + await sftp.upload(...); +} on SftpPermissionException { + showError('アクセスが拒否されました: ${stat.path}'); + showHint('ファイルの権限と所有者を確認してください'); +} +``` + +### 接続エラー + +```dart +try { + await sftp.listDir(path); +} on SftpConnectionException { + showError('接続が失われました'); + await reconnect(); +} +``` + +### スペースエラー + +```dart +try { + await sftp.upload(...); +} on SftpNoSpaceException { + showError('リモートサーバーのディスク容量が不足しています'); +} +``` + +## パフォーマンスの最適化 + +### ディレクトリキャッシュ + +```dart +class DirectoryCache { + final Map _cache = {}; + final Duration ttl = Duration(minutes: 5); + + Future> 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; + } +} +``` + +### レイジーロード + +巨大なディレクトリ (>1000 アイテム) の場合: + +```dart +List 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)); +} +``` + +### ページネーション + +```dart +class PaginatedDirectory { + static const pageSize = 100; + + Future> getPage(int page) async { + final offset = page * pageSize; + return await sftp.listDir( + path, + offset: offset, + limit: pageSize, + ); + } +} +``` diff --git a/docs/src/content/docs/ja/principles/ssh.md b/docs/src/content/docs/ja/principles/ssh.md new file mode 100644 index 00000000..c4ec3929 --- /dev/null +++ b/docs/src/content/docs/ja/principles/ssh.md @@ -0,0 +1,305 @@ +--- +title: SSH 接続 +description: SSH 接続の確立と管理の仕組み +--- + +Server Box における SSH 接続の仕組みについて解説します。 + +## 接続フロー + +```text +ユーザー入力 → Spi 構成 → genClient() → SSH クライアント → セッション +``` + +### ステップ 1: 構成 + +`Spi` (Server Parameter Info) モデルには以下が含まれます。 + +```dart +class Spi { + String id; // ユーザー ID / 一意の識別子 + String name; // サーバー名 + String ip; // IP アドレス + int port; // SSH ポート (デフォルト 22) + String user; // ユーザー名 + String? pwd; // パスワード (暗号化済み) + String? keyId; // SSH キー ID + String? jumpId; // 踏み台サーバー ID + String? alterUrl; // 代替 URL +} +``` + +### ステップ 2: クライアントの生成 + +`genClient(spi)` が SSH クライアントを作成します。 + +```dart +Future genClient(Spi spi) async { + // 1. ソケットを確立 + var socket = await connect(spi.ip, spi.port); + + // 2. 失敗した場合は代替 URL を試行 + if (socket == null && spi.alterUrl != null) { + socket = await connect(spi.alterUrl, spi.port); + } + + if (socket == null) { + throw ConnectionException('Unable to connect'); + } + + // 3. 認証 + final client = SSHClient( + socket: socket, + username: spi.user, + onPasswordRequest: () => spi.pwd, + onIdentityRequest: () => loadKey(spi.keyId), + ); + + // 4. ホストキーを検証 + await verifyHostKey(client, spi); + + return client; +} +``` + +### ステップ 3: 踏み台サーバー (設定されている場合) + +踏み台サーバーを経由する場合、再帰的に接続します。 + +```dart +if (spi.jumpId != null) { + final jumpClient = await genClient(getJumpSpi(spi.jumpId)); + final forwarded = await jumpClient.forwardLocal( + spi.ip, + spi.port, + ); + // 転送されたソケット経由で接続 +} +``` + +## 認証方法 + +### パスワード認証 + +```dart +onPasswordRequest: () => spi.pwd +``` + +- パスワードは Hive に暗号化して保存されます。 +- 接続時に復号されます。 +- 検証のためにサーバーに送信されます。 + +### 公開鍵認証 + +```dart +onIdentityRequest: () async { + final key = await KeyStore.get(spi.keyId); + return decyptPem(key.pem, key.password); +} +``` + +**キーのロードプロセス:** +1. `KeyStore` から暗号化されたキーを取得 +2. パスワードを復号 (生体認証または入力) +3. PEM 形式をパース +4. 改行コードを標準化 (LF) +5. 認証用に返却 + +### キーボードインタラクティブ (Keyboard-Interactive) + +```dart +onUserInfoRequest: (instructions) async { + // チャレンジ・レスポンスを処理 + return responses; +} +``` + +以下をサポートしています。 +- パスワード認証 +- OTP トークン +- 二要素認証 (2FA) + +## ホストキー検証 + +### なぜホストキーを検証するのか? + +正しいサーバーに接続していることを確認することで、**中間者攻撃 (MITM)** を防ぎます。 + +### 保存形式 + +```text +{spi.id}::{keyType} +``` + +例: +```text +my-server::ssh-ed25519 +my-server::ecdsa-sha2-nistp256 +``` + +### フィンガープリント形式 + +**MD5 Hex:** +```text +aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99 +``` + +**Base64:** +```text +SHA256:AbCdEf1234567890...= +``` + +### 検証フロー + +```dart +Future verifyHostKey(SSHClient client, Spi spi) async { + final key = await client.hostKey; + final keyType = key.type; + final fingerprint = md5Hex(key); // または base64 + + final stored = SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType']; + + if (stored == null) { + // 未知のホスト - ユーザーに確認 + final trust = await promptUser( + '未知のホスト', + 'フィンガープリント: $fingerprint', + ); + if (trust) { + SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType'] = fingerprint; + } + } else if (stored != fingerprint) { + // 変更されている - ユーザーに警告 + await warnUser( + 'ホストキーが変更されています!', + '中間者攻撃の可能性があります', + ); + } +} +``` + +## セッション管理 + +### 接続のプーリング + +`ServerProvider` でアクティブなクライアントを維持します。 + +```dart +class ServerProvider { + final Map _clients = {}; + + SSHClient getClient(String spiId) { + return _clients[spiId] ??= connect(spiId); + } +} +``` + +### Keep-Alive + +アイドル中の接続を維持します。 + +```dart +Timer.periodic( + Duration(seconds: 30), + (_) => client.sendKeepAlive(), +); +``` + +### 自動再接続 + +接続が失われた場合: + +```dart +client.onError.listen((error) async { + await Future.delayed(Duration(seconds: 5)); + reconnect(); +}); +``` + +## 接続ライフサイクル + +```text +┌─────────────┐ +│ 初期状態 │ +└──────┬──────┘ + │ connect() + ↓ +┌─────────────┐ +│ 接続中 │ ←──┐ +└──────┬──────┘ │ + │ 成功 │ + ↓ │ 失敗 (再試行) +┌─────────────┐ │ +│ 接続済み │───┘ +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ アクティブ│ ──→ コマンド送信 +└──────┬──────┘ + │ + ↓ (エラー/切断) +┌─────────────┐ +│ 切断済み │ +└─────────────┘ +``` + +## エラーハンドリング + +### 接続タイムアウト + +```dart +try { + await client.connect().timeout( + Duration(seconds: 30), + ); +} on TimeoutException { + throw ConnectionException('接続タイムアウト'); +} +``` + +### 認証失敗 + +```dart +onAuthFail: (error) { + if (error.contains('password')) { + return 'パスワードが正しくありません'; + } else if (error.contains('key')) { + return 'SSH キーが正しくありません'; + } + return '認証に失敗しました'; +} +``` + +### ホストキーの不一致 + +```dart +onHostKeyMismatch: (stored, current) { + showSecurityWarning( + 'ホストキーが変更されています!', + '中間者攻撃の可能性があります', + ); +} +``` + +## パフォーマンスに関する考慮事項 + +### 接続の再利用 + +- 機能間でクライアントを再利用する +- 不必要に切断・再接続を行わない +- 並行操作のために接続をプーリングする + +### 最適な設定 + +- **タイムアウト**: 30 秒 (調整可能) +- **Keep-alive**: 30 秒ごと +- **再試行遅延**: 5 秒 + +### ネットワーク効率 + +- 1 つの接続で複数の操作を行う +- 可能な場合はコマンドをパイプライン化する +- 複数の接続を同時に開くのを避ける diff --git a/docs/src/content/docs/ja/principles/state.md b/docs/src/content/docs/ja/principles/state.md new file mode 100644 index 00000000..49b7a569 --- /dev/null +++ b/docs/src/content/docs/ja/principles/state.md @@ -0,0 +1,167 @@ +--- +title: 状態管理 +description: Riverpod を使用した状態管理の仕組み +--- + +Server Box における状態管理アーキテクチャについて解説します。 + +## なぜ Riverpod なのか? + +**主な利点:** +- **コンパイル時の安全性**: エラーをコンパイル時に検知可能 +- **BuildContext が不要**: どこからでも状態にアクセス可能 +- **テストの容易さ**: Provider を単体で簡単にテスト可能 +- **コード生成**: ボイラープレートを削減し、型安全性を確保 + +## Provider アーキテクチャ + +``` +┌─────────────────────────────────────────────┐ +│ UI レイヤー (Widget) │ +│ - ConsumerWidget / ConsumerStatefulWidget │ +│ - ref.watch() / ref.read() │ +└─────────────────────────────────────────────┘ + ↓ 監視 (watch) +┌─────────────────────────────────────────────┐ +│ Provider レイヤー │ +│ - @riverpod アノテーション │ +│ - 生成された *.g.dart ファイル │ +└─────────────────────────────────────────────┘ + ↓ 使用 (use) +┌─────────────────────────────────────────────┐ +│ Service / Store レイヤー │ +│ - ビジネスロジック │ +│ - データアクセス │ +└─────────────────────────────────────────────┘ +``` + +## 使用されている Provider の種類 + +### 1. StateProvider (単純な状態) + +単純で観察可能な状態に使用します。 + +```dart +@riverpod +class ThemeNotifier extends _$ThemeNotifier { + @override + ThemeMode build() { + // 設定からロード + return SettingStore.themeMode; + } + + void setTheme(ThemeMode mode) { + state = mode; + SettingStore.themeMode = mode; // 永続化 + } +} +``` + +**使い方:** +```dart +class MyWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themeNotifierProvider); + return Text('現在のテーマ: $theme'); + } +} +``` + +### 2. AsyncNotifierProvider (非同期状態) + +非同期にロードされるデータに使用します。 + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + // 初回ロード + return await fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + return await fetchStatus(server); + }); + } +} +``` + +**使い方:** +```dart +final status = ref.watch(serverStatusProvider(server)); + +status.when( + data: (data) => StatusWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 3. StreamProvider (リアルタイムデータ) + +継続的なデータストリームに使用します。 + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + final client = ref.watch(sshClientProvider(server)); + final stream = client.monitorCpu(); + + // 監視されなくなったときに自動でリソースを解放 + ref.onDispose(() { + client.stopMonitoring(); + }); + + return stream; +} +``` + +**使い方:** +```dart +final cpu = ref.watch(cpuUsageProvider(server)); + +cpu.when( + data: (usage) => CpuChart(usage), + loading: () => CircularProgressIndicator(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 4. Family Provider (パラメータ付き) + +パラメータを受け取る Provider です。 + +```dart +@riverpod +Future> containers(ContainersRef ref, Server server) async { + final client = await ref.watch(sshClientProvider(server).future); + return await client.listContainers(); +} +``` + +**使い方:** +```dart +final containers = ref.watch(containersProvider(server)); + +// サーバーが異なれば、キャッシュされる状態も異なります +final containers2 = ref.watch(containersProvider(server2)); +``` + +## パフォーマンスの最適化 + +- **Provider Keep-Alive**: リスナーがいなくなっても破棄されないようにするには `@Riverpod(keepAlive: true)` を使用します。 +- **選択的な監視**: `select` を使用して、状態の特定の断片のみを監視します。 +- **Provider キャッシュ**: Family Provider はパラメータごとに結果をキャッシュします。 + +## ベストプラクティス + +1. **Provider を近くに配置する**: 利用する Widget の近くに定義します。 +2. **コード生成を利用する**: 常に `@riverpod` を使用します。 +3. **Provider の責務を絞る**: 単一責任の原則に従います。 +4. **ロード状態を適切に扱う**: AsyncValue の各状態を必ずハンドルします。 +5. **リソースを解放する**: クリーンアップには `ref.onDispose()` を使用します。 +6. **深い Provider ツリーを避ける**: Provider のグラフ構造はフラットに保ちます。 diff --git a/docs/src/content/docs/ja/principles/terminal.md b/docs/src/content/docs/ja/principles/terminal.md new file mode 100644 index 00000000..cfc0e12a --- /dev/null +++ b/docs/src/content/docs/ja/principles/terminal.md @@ -0,0 +1,198 @@ +--- +title: ターミナルの実装 +description: SSH ターミナルの内部的な仕組み +--- + +SSH ターミナルは、カスタマイズされた xterm.dart フォークをベースに構築された、最も複雑な機能の一つです。 + +## アーキテクチャの概要 + +``` +┌─────────────────────────────────────────────┐ +│ ターミナル UI レイヤー │ +│ - タブ管理 │ +│ - 仮想キーボード │ +│ - テキスト選択 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ xterm.dart エミュレータ │ +│ - PTY (擬似ターミナル) │ +│ - VT100/ANSI エミュレーション │ +│ - レンダリングエンジン │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SSH クライアントレイヤー │ +│ - SSH セッション │ +│ - チャネル管理 │ +│ - データストリーミング │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ リモートサーバー │ +│ - シェルプロセス │ +│ - コマンド実行 │ +└─────────────────────────────────────────────┘ +``` + +## ターミナルセッションのライフサイクル + +### 1. セッションの作成 + +```dart +Future createSession(Spi spi) async { + // 1. SSH クライアントを取得 + final client = await genClient(spi); + + // 2. PTY を開く + final pty = await client.openPty( + term: 'xterm-256color', + cols: 80, + rows: 24, + ); + + // 3. ターミナルエミュレータを初期化 + final terminal = Terminal( + backend: PtyBackend(pty), + ); + + // 4. リサイズハンドラを設定 + terminal.onResize.listen((size) { + pty.resize(size.cols, size.rows); + }); + + return TerminalSession( + terminal: terminal, + pty: pty, + client: client, + ); +} +``` + +### 2. ターミナルエミュレーション + +xterm.dart フォークは以下を提供します。 + +**VT100/ANSI エミュレーション:** +- カーソル移動 +- カラー(256 色対応) +- テキスト属性(太字、アンダーラインなど) +- スクロール領域 +- 代替画面バッファ + +**レンダリング:** +- 行ベースのレンダリング +- 双方向テキストのサポート +- Unicode/Emoji 対応 +- 描画の最適化 + +### 3. データフロー + +``` +ユーザー入力 + ↓ +仮想キーボード / 物理キーボード + ↓ +ターミナルエミュレータ (キー → エスケープシーケンス) + ↓ +SSH チャネル (送信) + ↓ +リモート PTY + ↓ +リモートシェル + ↓ +コマンド出力 + ↓ +SSH チャネル (受信) + ↓ +ターミナルエミュレータ (ANSI コードのパース) + ↓ +画面へのレンダリング +``` + +## マルチタブシステム + +### タブ管理 + +タブは画面遷移の間も状態を維持します。 +- SSH 接続の維持 +- ターミナル状態の保存 +- スクロールバッファの保持 +- 入力履歴の保持 + +## 仮想キーボード + +### プラットフォーム固有の実装 + +**iOS:** +- UIView ベースのカスタムキーボード +- キーボードボタンによる切り替え +- フォーカスに基づいた自動表示/非表示 + +**Android:** +- カスタム入力メソッド +- システムキーボードとの統合 +- クイックアクションボタン + +### キーボードボタン + +| ボタン | アクション | +|--------|--------| +| **切替** | システムキーボードの表示/非表示 | +| **Ctrl** | Ctrl 修飾キーの送信 | +| **Alt** | Alt 修飾キーの送信 | +| **SFTP** | 現在のディレクトリを開く | +| **クリップボード** | コンテキストに応じたコピー/ペースト | +| **スニペット** | スニペットの実行 | + +## テキスト選択 + +1. **長押し**: 選択モードに入る +2. **ドラッグ**: 選択範囲を広げる +3. **離す**: クリップボードにコピー + +## フォントと寸法 + +### サイズ計算 + +```dart +class TerminalDimensions { + static Size calculate(double fontSize, Size screenSize) { + final charWidth = fontSize * 0.6; // 等幅フォントのアスペクト比 + final charHeight = fontSize * 1.2; + + final cols = (screenSize.width / charWidth).floor(); + final rows = (screenSize.height / charHeight).floor(); + + return Size(cols.toDouble(), rows.toDouble()); + } +} +``` + +### ピンチズーム + +```dart +GestureDetector( + onScaleStart: () => _baseFontSize = currentFontSize, + onScaleUpdate: (details) { + final newFontSize = _baseFontSize * details.scale; + resize(newFontSize); + }, +) +``` + +## カラースキーム + +- **ライト (Light)**: 明るい背景、暗いテキスト +- **ダーク (Dark)**: 暗い背景、明るいテキスト +- **AMOLED**: 真っ黒な背景 + +## パフォーマンスの最適化 + +- **Dirty rectangle**: 変更された領域のみを再描画 +- **行キャッシュ**: レンダリングされた行をキャッシュ +- **レイジースクロール**: 長いバッファのための仮想スクロール +- **バッチ更新**: 複数の書き込みを統合 +- **圧縮**: スクロールバッファの圧縮 +- **デバウンス**: 素早い入力に対するデバウンス処理 diff --git a/docs/src/content/docs/ja/quick-start.mdx b/docs/src/content/docs/ja/quick-start.mdx new file mode 100644 index 00000000..45050c0a --- /dev/null +++ b/docs/src/content/docs/ja/quick-start.mdx @@ -0,0 +1,45 @@ +--- +title: クイックスタート +description: Server Box を数分で使い始める +--- + +このクイックスタートガイドに従って、最初のサーバーに接続し、監視を開始しましょう。 + +## ステップ 1: サーバーを追加する + +1. Server Box を開きます +2. **+** ボタンをタップして新しいサーバーを追加します +3. サーバー情報を入力します: + - **名前**: サーバーの分かりやすい名前 + - **ホスト**: IP アドレスまたはドメイン名 + - **ポート**: SSH ポート(デフォルト:22) + - **ユーザー**: SSH ユーザー名 + - **パスワードまたは鍵**: 認証方法 + +4. **保存** をタップしてサーバーを追加します + +## ステップ 2: 接続と監視 + +1. サーバーカードをタップして接続します +2. アプリが SSH 接続を確立します +3. 以下のリアルタイムステータスが表示されます: + - CPU 使用率 + - メモリ (RAM) とスワップ + - ディスク使用量 + - ネットワーク速度 + +## ステップ 3: 機能を探索する + +接続すると、以下のことができます: + +- **ターミナルを開く**: ターミナルボタンをタップしてフル SSH アクセスを利用する +- **ファイルを閲覧する**: SFTP を使用してファイルを管理する +- **コンテナを管理する**: Docker コンテナを表示および制御する +- **プロセスを表示する**: 実行中のプロセスを確認する +- **スニペットを実行する**: 保存されたコマンドを実行する + +## ヒント + +- **生体認証**: Face ID / Touch ID / 指紋認証を有効にして、すばやくアクセス(モバイル) +- **ホーム画面ウィジェット**: サーバーのステータスウィジェットをホーム画面に追加(iOS/Android) +- **バックグラウンド実行**: バックグラウンドで接続を維持(Android) diff --git a/docs/src/content/docs/platforms/desktop.md b/docs/src/content/docs/platforms/desktop.md index baa418d0..2aa3ee95 100644 --- a/docs/src/content/docs/platforms/desktop.md +++ b/docs/src/content/docs/platforms/desktop.md @@ -3,7 +3,7 @@ title: Desktop Features description: macOS, Linux, and Windows specific features --- -Flutter Server Box on desktop platforms provides additional productivity features. +Server Box on desktop platforms provides additional productivity features. ## macOS diff --git a/docs/src/content/docs/platforms/mobile.md b/docs/src/content/docs/platforms/mobile.md index 8e5a8547..955810b1 100644 --- a/docs/src/content/docs/platforms/mobile.md +++ b/docs/src/content/docs/platforms/mobile.md @@ -3,7 +3,7 @@ title: Mobile Features description: iOS and Android specific features --- -Flutter Server Box provides several mobile-specific features for iOS and Android devices. +Server Box provides several mobile-specific features for iOS and Android devices. ## Biometric Authentication @@ -22,7 +22,7 @@ Add server status widgets to your home screen for quick monitoring. - Long press on home screen - Tap **+** to add widget -- Search for "Flutter Server Box" +- Search for "Server Box" - Choose widget size: - Small: Single server status - Medium: Multiple servers @@ -32,7 +32,7 @@ Add server status widgets to your home screen for quick monitoring. - Long press on home screen - Tap **Widgets** -- Find "Flutter Server Box" +- Find "Server Box" - Select widget type ## Background Running diff --git a/docs/src/content/docs/platforms/watchos.md b/docs/src/content/docs/platforms/watchos.md deleted file mode 100644 index 4513912e..00000000 --- a/docs/src/content/docs/platforms/watchos.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: watchOS App -description: Apple Watch companion app ---- - -Flutter Server Box includes a companion app for Apple Watch, providing quick server monitoring on your wrist. - -## Features - -### Server Status at a Glance - -- View server online/offline status -- Quick CPU and memory stats -- One-tap server connectivity check - -### Complications - -Add server information to your watch face: - -- **Modular**: Server status icon -- **Infograph**: Small complication -- **Extra Large**: Large complication - -### Quick Actions - -- Ping servers -- Wake on LAN -- Quick terminal command - -## Requirements - -- Apple Watch Series 3 or later -- watchOS 8.0 or later -- Paired iPhone with Flutter Server Box installed -- iPhone and Watch on same Wi-Fi - -## Setup - -1. Install Flutter Server Box on iPhone -2. Open the Watch app on iPhone -3. Find Flutter Server Box under "Available Apps" -4. Toggle "Show App on Apple Watch" - -## Limitations - -- No full terminal access -- Reduced SFTP functionality -- Dependent on iPhone connection -- Simplified server management - -## Tips - -- Use complications for persistent status -- Quick actions for emergency checks -- Sync servers from iPhone app diff --git a/docs/src/content/docs/principles/architecture.md b/docs/src/content/docs/principles/architecture.md index 181398b8..39887795 100644 --- a/docs/src/content/docs/principles/architecture.md +++ b/docs/src/content/docs/principles/architecture.md @@ -3,7 +3,7 @@ title: Architecture Overview description: High-level application architecture --- -Flutter Server Box follows a layered architecture with clear separation of concerns. +Server Box follows a layered architecture with clear separation of concerns. ## Architecture Layers diff --git a/docs/src/content/docs/principles/ssh.md b/docs/src/content/docs/principles/ssh.md index 5b759422..0e907711 100644 --- a/docs/src/content/docs/principles/ssh.md +++ b/docs/src/content/docs/principles/ssh.md @@ -3,7 +3,7 @@ title: SSH Connection description: How SSH connections are established and managed --- -Understanding SSH connections in Flutter Server Box. +Understanding SSH connections in Server Box. ## Connection Flow diff --git a/docs/src/content/docs/principles/state.md b/docs/src/content/docs/principles/state.md index 098a0e25..538bfd77 100644 --- a/docs/src/content/docs/principles/state.md +++ b/docs/src/content/docs/principles/state.md @@ -3,7 +3,7 @@ title: State Management description: How state is managed with Riverpod --- -Understanding the state management architecture in Flutter Server Box. +Understanding the state management architecture in Server Box. ## Why Riverpod? diff --git a/docs/src/content/docs/quick-start.mdx b/docs/src/content/docs/quick-start.mdx index 948eca68..a41a3c60 100644 --- a/docs/src/content/docs/quick-start.mdx +++ b/docs/src/content/docs/quick-start.mdx @@ -1,13 +1,13 @@ --- title: Quick Start -description: Get up and running with Flutter Server Box in minutes +description: Get up and running with Server Box in minutes --- Follow this quick start guide to connect to your first server and start monitoring. ## Step 1: Add a Server -1. Open Flutter Server Box +1. Open Server Box 2. Tap the **+** button to add a new server 3. Fill in the server information: - **Name**: A friendly name for your server @@ -43,9 +43,3 @@ Once connected, you can: - **Biometric Authentication**: Enable Face ID / Touch ID / Fingerprint for quick access (mobile) - **Home Screen Widgets**: Add server status widgets to your home screen (iOS/Android) - **Background Running**: Keep connections alive in the background (Android) - -## Next Steps - -- Explore the [Features](/features/) section for detailed guides -- Configure [SSH Keys](/configuration/ssh-keys/) for passwordless authentication -- Customize your experience in [Settings](/configuration/appearance/) diff --git a/docs/src/content/docs/zh/advanced/bulk-import.md b/docs/src/content/docs/zh/advanced/bulk-import.md new file mode 100644 index 00000000..056726ce --- /dev/null +++ b/docs/src/content/docs/zh/advanced/bulk-import.md @@ -0,0 +1,83 @@ +--- +title: 批量导入服务器 +description: 从 JSON 文件中导入多个服务器 +--- + +使用 JSON 文件一次性导入多个服务器配置。 + +## JSON 格式 + +:::danger[安全警告] +**切勿在文件中存储明文密码!** 此 JSON 示例仅为了演示显示了密码字段,但你应该: + +- **优先使用 SSH 密钥** (`keyId`) 而不是 `pwd` - 它们更安全 +- 如果必须使用密码,请使用**密码管理器**或环境变量 +- 导入后**立即删除文件** - 不要让凭据散落在各处 +- **添加到 .gitignore** - 切勿将凭证文件提交到版本控制 +::: + +```json +[ + { + "name": "My Server", + "ip": "example.com", + "port": 22, + "user": "root", + "pwd": "password", + "keyId": "", + "tags": ["production"], + "autoConnect": false + } +] +``` + +## 字段说明 + +| 字段 | 必填 | 说明 | +|-------|----------|-------------| +| `name` | 是 | 显示名称 | +| `ip` | 是 | 域名或 IP 地址 | +| `port` | 是 | SSH 端口 (通常为 22) | +| `user` | 是 | SSH 用户名 | +| `pwd` | 否 | 密码 (不建议使用 - 请改用 SSH 密钥) | +| `keyId` | 否 | SSH 密钥名称 (来自“私钥” - 推荐) | +| `tags` | 否 | 组织标签 | +| `autoConnect` | 否 | 启动时自动连接 | + +## 导入步骤 + +1. 创建包含服务器配置的 JSON 文件 +2. 设置 → 备份 → 批量导入服务器 +3. 选择你的 JSON 文件 +4. 确认导入 + +## 示例 + +```json +[ + { + "name": "Production", + "ip": "prod.example.com", + "port": 22, + "user": "admin", + "keyId": "my-key", + "tags": ["production", "web"] + }, + { + "name": "Development", + "ip": "dev.example.com", + "port": 2222, + "user": "dev", + "keyId": "dev-key", + "tags": ["development"] + } +] +``` + +## 提示 + +- 尽可能**使用 SSH 密钥**代替密码 +- 导入后**测试连接** +- **使用标签组织**以便于管理 +- 导入后**删除 JSON 文件** +- **切勿提交**包含凭据的 JSON 文件到版本控制 diff --git a/docs/src/content/docs/zh/advanced/custom-commands.md b/docs/src/content/docs/zh/advanced/custom-commands.md new file mode 100644 index 00000000..a48084a2 --- /dev/null +++ b/docs/src/content/docs/zh/advanced/custom-commands.md @@ -0,0 +1,72 @@ +--- +title: 自定义命令 +description: 在服务器页面上显示自定义命令输出 +--- + +添加自定义 shell 命令,以在服务器详情页面上显示其输出。 + +## 设置步骤 + +1. 服务器设置 → 自定义命令 +2. 以 JSON 格式输入命令 + +## 基础格式 + +```json +{ + "显示名称": "shell 命令" +} +``` + +**示例:** +```json +{ + "内存": "free -h", + "磁盘": "df -h", + "运行时间": "uptime" +} +``` + +## 查看结果 + +设置完成后,自定义命令将显示在服务器详情页面上,并自动刷新。 + +## 特殊命令名称 + +### server_card_top_right + +显示在首页服务器卡片的右上角: + +```json +{ + "server_card_top_right": "你的命令" +} +``` + +## 提示 + +**使用绝对路径:** +```json +{"我的脚本": "/usr/local/bin/my-script.sh"} +``` + +**管道命令:** +```json +{"占用最高进程": "ps aux | sort -rk 3 | head -5"} +``` + +**格式化输出:** +```json +{"CPU 负载": "uptime | awk -F'load average:' '{print $2}'"} +``` + +**保持命令快速执行:** 最好在 5 秒内完成,以获得最佳体验。 + +**限制输出内容:** +```json +{"日志": "tail -20 /var/log/syslog"} +``` + +## 安全性 + +命令以 SSH 用户的权限运行。避免使用修改系统状态的命令。 diff --git a/docs/src/content/docs/zh/advanced/custom-logo.md b/docs/src/content/docs/zh/advanced/custom-logo.md new file mode 100644 index 00000000..e026c29f --- /dev/null +++ b/docs/src/content/docs/zh/advanced/custom-logo.md @@ -0,0 +1,54 @@ +--- +title: 自定义服务器 Logo +description: 为服务器卡片使用自定义图标 +--- + +通过图片 URL 在服务器卡片上显示自定义 Logo。 + +## 设置步骤 + +1. 服务器设置 → 自定义 Logo +2. 输入图片 URL + +## URL 占位符 + +### {DIST} - Linux 发行版 + +自动替换为检测到的发行版: + +``` +https://example.com/{DIST}.png +``` + +将变为:`debian.png`, `ubuntu.png`, `arch.png` 等。 + +### {BRIGHT} - 主题 + +自动替换为当前主题: + +``` +https://example.com/{BRIGHT}.png +``` + +将变为:`light.png` 或 `dark.png`。 + +### 组合使用 + +``` +https://example.com/{DIST}-{BRIGHT}.png +``` + +将变为:`debian-light.png`, `ubuntu-dark.png` 等。 + +## 提示 + +- 使用 PNG 或 SVG 格式 +- 建议尺寸:64x64 到 128x128 像素 +- 使用 HTTPS URL +- 保持文件体积较小 + +## 支持的发行版 + +debian, ubuntu, centos, fedora, opensuse, kali, alpine, arch, rocky, deepin, armbian, wrt + +完整列表:[`dist.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/model/server/dist.dart) diff --git a/docs/src/content/docs/zh/advanced/json-settings.md b/docs/src/content/docs/zh/advanced/json-settings.md new file mode 100644 index 00000000..cb23e49b --- /dev/null +++ b/docs/src/content/docs/zh/advanced/json-settings.md @@ -0,0 +1,64 @@ +--- +title: 隐藏设置 (JSON) +description: 通过 JSON 编辑器访问高级设置 +--- + +有些设置在 UI 中是隐藏的,但可以通过 JSON 编辑器进行访问。 + +## 如何访问 + +长按侧边栏中的**“设置”**即可打开 JSON 编辑器。 + +## 常用隐藏设置 + +### timeOut + +连接超时时间(秒)。 + +```json +{"timeOut": 10} +``` + +**类型:** 整数 | **默认值:** 5 | **范围:** 1-60 + +### recordHistory + +保存历史记录(SFTP 路径等)。 + +```json +{"recordHistory": true} +``` + +**类型:** 布尔值 | **默认值:** true + +### textFactor + +文本缩放系数。 + +```json +{"textFactor": 1.2} +``` + +**类型:** 浮点数 | **默认值:** 1.0 | **范围:** 0.8-1.5 + +## 查找更多设置 + +所有设置都定义在 [`setting.dart`](https://github.com/lollipopkit/flutter_server_box/blob/main/lib/data/store/setting.dart) 中。 + +查找如下代码: +```dart +late final settingName = StoreProperty(box, 'settingKey', defaultValue); +``` + +## ⚠️ 重要提示 + +**在编辑之前:** +- **创建备份** - 错误的设置可能导致应用无法打开 +- **谨慎编辑** - JSON 必须有效 + +## 恢复方法 + +如果编辑后应用无法打开: +1. 清除应用数据(最后手段) +2. 重新安装应用 +3. 从备份恢复 diff --git a/docs/src/content/docs/zh/advanced/troubleshooting.md b/docs/src/content/docs/zh/advanced/troubleshooting.md new file mode 100644 index 00000000..3b64e173 --- /dev/null +++ b/docs/src/content/docs/zh/advanced/troubleshooting.md @@ -0,0 +1,118 @@ +--- +title: 常见问题 +description: 常见问题的解决方案 +--- + +## 连接问题 + +### SSH 无法连接 + +**症状:** 超时、连接被拒绝、身份验证失败 + +**解决方案:** + +1. **核对服务器类型:** 仅支持类 Unix 系统(Linux、macOS、Android/Termux) +2. **手动测试:** 在终端运行 `ssh user@server -p port` +3. **检查防火墙:** 确保 22 端口已开放 +4. **核对凭据:** 确保用户名、密码或密钥正确 + +### 频繁掉线 + +**症状:** 终端在闲置一段时间后自动断开 + +**解决方案:** + +1. **服务器心跳设置:** + ```bash + # /etc/ssh/sshd_config + ClientAliveInterval 60 + ClientAliveCountMax 3 + ``` + +2. **禁用电池优化:** + - MIUI:电池 → “不限制” + - Android:设置 → 应用 → 禁用电池优化 + - iOS:开启后台刷新 + +## 输入问题 + +### 无法输入某些字符 + +**解决方案:** 设置 → 键盘类型 → 切换为 `visiblePassword` + +注意:更改此项后,CJK(中日韩)输入法可能无法正常工作。 + +## 应用问题 + +### 应用在启动时崩溃 + +**症状:** 应用打不开、黑屏 + +**原因:** 设置损坏,特别是通过 JSON 编辑器修改后 + +**解决方案:** + +1. **清除应用数据:** + - Android:设置 → 应用 → ServerBox → 清除数据 + - iOS:删除并重新安装 + +2. **恢复备份:** 导入更改设置前创建的备份文件 + +### 备份/恢复失败 + +**备份失败:** +- 检查存储空间 +- 确保应用具有存储权限 +- 尝试不同的存储位置 + +**恢复失败:** +- 检查备份文件的完整性 +- 检查应用版本兼容性 + +## 桌面组件(Widget)问题 + +### 组件不更新 + +**iOS:** +- 自动刷新最多可能需要等待 30 分钟 +- 尝试删除并重新添加组件 +- 检查 URL 是否以 `/status` 结尾 + +**Android:** +- 点击组件以强制刷新 +- 确保组件 ID 与应用设置中的配置一致 + +**watchOS:** +- 重启手表应用 +- 更改配置后等待几分钟 +- 核对 URL 格式 + +### 组件显示错误 + +- 确保服务器上正在运行 ServerBox Monitor +- 在浏览器中测试 URL +- 检查身份验证凭据 + +## 性能问题 + +### 应用响应慢 + +**解决方案:** +- 在设置中降低刷新频率 +- 检查网络速度 +- 禁用不常用的服务器 + +### 耗电量高 + +**解决方案:** +- 增加刷新间隔 +- 禁用后台刷新 +- 关闭不使用的 SSH 会话 + +## 获取帮助 + +如果问题仍然存在: + +1. **搜索 GitHub Issues:** https://github.com/lollipopkit/flutter_server_box/issues +2. **提交新 Issue:** 请包含应用版本、平台和复现步骤 +3. **查看 Wiki:** 本文档以及 GitHub Wiki diff --git a/docs/src/content/docs/zh/advanced/widgets.md b/docs/src/content/docs/zh/advanced/widgets.md new file mode 100644 index 00000000..4aae3fff --- /dev/null +++ b/docs/src/content/docs/zh/advanced/widgets.md @@ -0,0 +1,90 @@ +--- +title: 主屏幕小组件 +description: 在主屏幕上添加服务器状态小组件 +--- + +需要在服务器上安装 [ServerBox Monitor](https://github.com/lollipopkit/server_box_monitor)。 + +## 前置条件 + +请先在你的服务器上安装 ServerBox Monitor。安装说明请参考 [ServerBox Monitor Wiki](https://github.com/lollipopkit/server_box_monitor/wiki/Home)。 + +安装完成后,你的服务器应具备: +- HTTP/HTTPS 端点 +- `/status` API 接口 +- 可选的身份验证 + +## URL 格式 + +``` +https://your-server.com/status +``` + +必须以 `/status` 结尾。 + +## iOS 小组件 + +### 设置步骤 + +1. 长按主屏幕 → 点击 **+** +2. 搜索 “ServerBox” +3. 选择小组件尺寸 +4. 长按小组件 → **编辑小组件** +5. 输入以 `/status` 结尾的 URL + +### 注意事项 + +- 必须使用 HTTPS(局域网 IP 除外) +- 最大刷新频率:30 分钟(iOS 系统限制) +- 可以为多个服务器添加多个小组件 + +## Android 小组件 + +### 设置步骤 + +1. 长按主屏幕 → **小组件** +2. 找到 “ServerBox” → 添加到主屏幕 +3. 记下显示的 Widget ID 数字 +4. 打开 ServerBox 应用 → 设置 +5. 点击 **配置桌面小组件链接** +6. 添加条目:`Widget ID` = `状态 URL` + +示例: +- 键 (Key):`17` +- 值 (Value):`https://my-server.com/status` + +7. 点击主屏幕上的小组件进行刷新 + +## watchOS 小组件 + +### 设置步骤 + +1. 打开 iPhone 上的应用 → 设置 +2. **iOS 设置** → **Watch 应用** +3. 点击 **添加 URL** +4. 输入以 `/status` 结尾的 URL +5. 等待手表端同步 + +### 注意事项 + +- 如果未更新,请尝试重启手表应用 +- 确保手机和手表已连接 + +## 故障排除 + +### 小组件不更新 + +**iOS:** 等待最多 30 分钟,然后尝试删除并重新添加。 +**Android:** 点击小组件强制刷新,检查设置中的 ID 是否正确。 +**watchOS:** 重启手表应用,等待几分钟。 + +### 小组件显示错误 + +- 确保 ServerBox Monitor 正在运行 +- 在浏览器中测试 URL 是否可用 +- 检查 URL 是否以 `/status` 结尾 + +## 安全性 + +- 尽可能**始终使用 HTTPS** +- **局域网 IP** 仅建议在信任的网络中使用 diff --git a/docs/src/content/docs/zh/development/architecture.md b/docs/src/content/docs/zh/development/architecture.md new file mode 100644 index 00000000..74cc8308 --- /dev/null +++ b/docs/src/content/docs/zh/development/architecture.md @@ -0,0 +1,86 @@ +--- +title: 架构 +description: 架构模式与设计决策 +--- + +Server Box 遵循整洁架构 (Clean Architecture) 原则,在数据层、领域层和表现层之间实现了清晰的分离。 + +## 分层架构 + +``` +┌─────────────────────────────────────┐ +│ 表现层 (Presentation) │ +│ (lib/view/page/) │ +│ - 页面、组件、控制器 │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ 业务逻辑层 (Business Logic) │ +│ (lib/data/provider/) │ +│ - Riverpod Provider │ +│ - 状态管理 │ +└─────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────┐ +│ 数据层 (Data) │ +│ (lib/data/model/, store/) │ +│ - 模型、存储、服务 │ +└─────────────────────────────────────┘ +``` + +## 关键模式 + +### 状态管理:Riverpod + +- **代码生成**:使用 `riverpod_generator` 实现类型安全的 provider +- **State Notifiers**:用于包含业务逻辑的可变状态 +- **Async Notifiers**:用于处理加载中和错误状态 +- **Stream Providers**:用于处理实时数据 + +### 不可变模型:Freezed + +- 所有数据模型均使用 Freezed 实现不可变性 +- 使用联合类型 (Union types) 表示不同状态 +- 内置 JSON 序列化支持 +- 提供 CopyWith 扩展以便于更新 + +### 本地存储:Hive + +- **hive_ce**:Hive 的社区版 +- 无需手动添加 `@HiveField` 或 `@HiveType` 注解 +- 类型适配器 (Type adapters) 自动生成 +- 持久化键值对存储 + +## 依赖注入 (DI) + +服务和存储类通过以下方式注入: + +1. **Provider**:向 UI 层暴露依赖 +2. **GetIt**:服务定位器(在适用情况下使用) +3. **构造函数注入**:显式声明依赖关系 + +## 数据流 + +``` +用户操作 → Widget → Provider → 服务/存储 → 模型更新 → UI 重构 +``` + +1. 用户与组件交互 +2. 组件调用 provider 方法 +3. Provider 通过服务/存储更新状态 +4. 状态更改触发 UI 重新构建 +5. 组件反映最新状态 + +## 自定义依赖 + +项目使用了多个自定义分支以扩展功能: + +- **dartssh2**:增强的 SSH 功能 +- **xterm**:支持移动端的终端模拟器 +- **fl_lib**:共享的 UI 组件和工具类 + +## 多线程处理 + +- **Isolates**:将繁重的计算任务移出主线程 +- **computer 包**:多线程工具类 +- **Async/Await**:非阻塞式 I/O 操作 diff --git a/docs/src/content/docs/zh/development/building.md b/docs/src/content/docs/zh/development/building.md new file mode 100644 index 00000000..ed1c66f4 --- /dev/null +++ b/docs/src/content/docs/zh/development/building.md @@ -0,0 +1,116 @@ +--- +title: 构建指南 +description: 不同平台的构建说明 +--- + +Server Box 使用自定义构建系统 (`fl_build`) 进行跨平台构建。 + +## 前置条件 + +- Flutter SDK (stable channel) +- 平台特定工具 (iOS 需要 Xcode,Android 需要 Android Studio) +- Rust 工具链 (部分原生依赖项需要) + +## 开发版构建 + +```bash +# 以开发模式运行 +flutter run + +# 在指定设备上运行 +flutter run -d +``` + +## 生产版构建 + +项目使用 `fl_build` 进行构建: + +```bash +# 为指定平台构建 +dart run fl_build -p + +# 可用平台: +# - ios +# - android +# - macos +# - linux +# - windows +``` + +## 平台特定构建 + +### iOS + +```bash +dart run fl_build -p ios +``` + +需要: +- 安装了 Xcode 的 macOS +- CocoaPods +- 用于签名的 Apple Developer 账号 + +### Android + +```bash +dart run fl_build -p android +``` + +需要: +- Android SDK +- Java Development Kit (JDK) +- 用于签名的 Keystore + +### macOS + +```bash +dart run fl_build -p macos +``` + +### Linux + +```bash +dart run fl_build -p linux +``` + +### Windows + +```bash +dart run fl_build -p windows +``` + +需要安装了 Visual Studio 的 Windows 环境。 + +## 构建前/后处理 + +`make.dart` 脚本负责处理: + +- 元数据生成 +- 版本字符串更新 +- 平台特定的配置 + +## 故障排除 + +### 全新构建 (Clean Build) + +```bash +flutter clean +dart run build_runner build --delete-conflicting-outputs +flutter pub get +``` + +### 版本不匹配 + +确保所有依赖项兼容: +```bash +flutter pub upgrade +``` + +## 发布清单 + +1. 更新 `pubspec.yaml` 中的版本号 +2. 运行代码生成 +3. 运行测试 +4. 为所有目标平台构建 +5. 在真机上测试 +6. 创建 GitHub Release diff --git a/docs/src/content/docs/zh/development/codegen.md b/docs/src/content/docs/zh/development/codegen.md new file mode 100644 index 00000000..c6c6d1b6 --- /dev/null +++ b/docs/src/content/docs/zh/development/codegen.md @@ -0,0 +1,98 @@ +--- +title: 代码生成 +description: 使用 build_runner 进行代码生成 +--- + +Server Box 大量使用代码生成技术来处理模型、状态管理和序列化。 + +## 何时运行代码生成 + +在修改以下内容后需要运行: + +- 带有 `@freezed` 注解的模型 +- 带有 `@JsonSerializable` 的类 +- Hive 模型 +- 带有 `@riverpod` 的 Provider +- 本地化文件 (ARB 文件) + +## 运行代码生成 + +```bash +# 生成所有代码 +dart run build_runner build --delete-conflicting-outputs + +# 清理并重新生成 +dart run build_runner build --delete-conflicting-outputs --clean +``` + +## 生成的文件类型 + +### Freezed (`*.freezed.dart`) + +具有联合类型 (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; +} +``` + +### JSON 序列化 (`*.g.dart`) + +由 `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 json) => + _$ServerFromJson(json); + Map toJson() => _$ServerToJson(this); +} +``` + +### Riverpod Provider (`*.g.dart`) + +由 `@riverpod` 注解生成: + +```dart +@riverpod +class MyNotifier extends _$MyNotifier { + @override + int build() => 0; +} +``` + +### Hive 适配器 (`*.g.dart`) + +为 Hive 模型 (hive_ce) 自动生成: + +```dart +@HiveType(typeId: 0) +class ServerModel { + @HiveField(0) + final String id; +} +``` + +## 生成本地化代码 + +```bash +flutter gen-l10n +``` + +根据 `lib/l10n/*.arb` 文件生成 `lib/generated/l10n/` 目录下的代码。 + +## 提示 + +- 使用 `--delete-conflicting-outputs` 避免冲突 +- 将生成的文件添加到 `.gitignore` +- **切勿**手动编辑生成的文件 diff --git a/docs/src/content/docs/zh/development/state.md b/docs/src/content/docs/zh/development/state.md new file mode 100644 index 00000000..1ea35044 --- /dev/null +++ b/docs/src/content/docs/zh/development/state.md @@ -0,0 +1,115 @@ +--- +title: 状态管理 +description: 基于 Riverpod 的状态管理模式 +--- + +Server Box 使用 Riverpod 及其代码生成工具进行状态管理。 + +## Provider 类型 + +### StateProvider + +可读写的简单状态: + +```dart +@riverpod +class Settings extends _$Settings { + @override + SettingsModel build() { + return SettingsModel.defaults(); + } + + void update(SettingsModel newSettings) { + state = newSettings; + } +} +``` + +### AsyncNotifierProvider + +具有加载中/错误状态的异步加载状态: + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + return fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => fetchStatus(server)); + } +} +``` + +### StreamProvider + +来自数据流的实时数据: + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + return cpuService.monitor(server); +} +``` + +## 状态模式 + +### 加载状态处理 + +```dart +state.when( + data: (data) => DataWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### Family Provider (带参数的 Provider) + +带有参数的 Provider: + +```dart +@riverpod +List containers(ContainersRef ref, Server server) { + return containerService.list(server); +} +``` + +### 自动释放 (Auto-Dispose) + +当不再被引用时自动销毁的 Provider: + +```dart +@Riverpod(keepAlive: false) +class TempState extends _$TempState { + // ... +} +``` + +## 最佳实践 + +1. **使用代码生成**:始终使用 `@riverpod` 注解。 +2. **就近放置 Provider**:将 Provider 定义在消费它的 Widget 附近。 +3. **避免使用单例**:改用 Provider。 +4. **正确的分层**:保持 UI 逻辑与业务逻辑的分离。 + +## 在 Widget 中读取状态 + +```dart +class ServerWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final status = ref.watch(serverStatusProvider(server)); + return status.when(...); + } +} +``` + +## 修改状态 + +```dart +ref.read(settingsProvider.notifier).update(newSettings); +``` diff --git a/docs/src/content/docs/zh/development/structure.md b/docs/src/content/docs/zh/development/structure.md new file mode 100644 index 00000000..4cb8622b --- /dev/null +++ b/docs/src/content/docs/zh/development/structure.md @@ -0,0 +1,96 @@ +--- +title: 项目结构 +description: 了解 Server Box 的代码库结构 +--- + +Server Box 项目遵循模块化架构,具有清晰的关注点分离。 + +## 目录结构 + +``` +lib/ +├── core/ # 核心工具类和扩展 +├── data/ # 数据层 +│ ├── model/ # 按功能划分的数据模型 +│ ├── provider/ # Riverpod provider +│ └── store/ # 本地存储 (Hive) +├── view/ # UI 层 +│ ├── page/ # 主要页面 +│ └── widget/ # 可复用组件 +├── generated/ # 生成的本地化代码 +├── l10n/ # 本地化 ARB 文件 +└── hive/ # Hive 适配器 +``` + +## 核心层 (`lib/core/`) + +包含工具类、扩展和路由配置: + +- **Extensions**:针对通用类型的 Dart 扩展 +- **Routes**:应用路由配置 +- **Utils**:共享的工具函数 + +## 数据层 (`lib/data/`) + +### 模型 (`lib/data/model/`) + +按功能模块组织: + +- `server/` - 服务器连接及状态模型 +- `container/` - Docker 容器模型 +- `ssh/` - SSH 会话模型 +- `sftp/` - SFTP 文件模型 +- `app/` - 应用特定的模型 + +### Provider (`lib/data/provider/`) + +用于依赖注入和状态管理的 Riverpod provider: + +- 服务器 Provider +- UI 状态 Provider +- 服务 Provider + +### 存储 (`lib/data/store/`) + +基于 Hive 的本地存储: + +- 服务器存储 +- 设置存储 +- 缓存存储 + +## 视图层 (`lib/view/`) + +### 页面 (`lib/view/page/`) + +应用程序的主要屏幕: + +- `server/` - 服务器管理页面 +- `ssh/` - SSH 终端页面 +- `container/` - 容器管理页面 +- `setting/` - 设置页面 +- `storage/` - SFTP 页面 +- `snippet/` - 脚本页面 + +### 组件 (`lib/view/widget/`) + +可复用的 UI 组件: + +- 服务器卡片 +- 状态图表 +- 输入组件 +- 对话框 + +## 生成的文件 + +- `lib/generated/l10n/` - 自动生成的本地化代码 +- `*.g.dart` - 生成的代码 (json_serializable, freezed, hive, riverpod) +- `*.freezed.dart` - Freezed 不可变类 + +## Packages 目录 (`/packages/`) + +包含依赖项的自定义分支: + +- `dartssh2/` - SSH 库 +- `xterm/` - 终端模拟器 +- `fl_lib/` - 共享工具类 +- `fl_build/` - 构建系统 diff --git a/docs/src/content/docs/zh/development/testing.md b/docs/src/content/docs/zh/development/testing.md new file mode 100644 index 00000000..6ef7a0ed --- /dev/null +++ b/docs/src/content/docs/zh/development/testing.md @@ -0,0 +1,113 @@ +--- +title: 测试指南 +description: 测试策略与运行测试 +--- + +## 运行测试 + +```bash +# 运行所有测试 +flutter test + +# 运行特定测试文件 +flutter test test/battery_test.dart + +# 运行测试并生成覆盖率报告 +flutter test --coverage +``` + +## 测试结构 + +测试文件位于 `test/` 目录中,其结构与 `lib` 目录保持一致: + +``` +test/ +├── data/ +│ ├── model/ +│ └── provider/ +├── view/ +│ └── widget/ +└── test_helpers.dart +``` + +## 单元测试 + +测试业务逻辑和数据模型: + +```dart +test('应当计算 CPU 百分比', () { + final cpu = CpuModel(usage: 75.0); + expect(cpu.usagePercentage, '75%'); +}); +``` + +## Widget 测试 + +测试 UI 组件: + +```dart +testWidgets('ServerCard 应当显示服务器名称', (tester) async { + await tester.pumpWidget( + ProviderScope( + child: MaterialApp( + home: ServerCard(server: testServer), + ), + ), + ); + + expect(find.text('Test Server'), findsOneWidget); +}); +``` + +## Provider 测试 + +测试 Riverpod provider: + +```dart +test('serverStatusProvider 应当返回状态', () async { + final container = ProviderContainer(); + final status = await container.read(serverStatusProvider(testServer).future); + expect(status, isA()); +}); +``` + +## Mock 模拟 + +对外部依赖使用 Mock 模拟: + +```dart +class MockSshService extends Mock implements SshService {} + +test('应当能连接到服务器', () async { + final mockSsh = MockSshService(); + when(mockSsh.connect(any)).thenAnswer((_) async => true); + + // 使用 mock 进行测试 +}); +``` + +## 集成测试 + +测试完整的用户流程(位于 `integration_test/`): + +```dart +testWidgets('添加服务器流程', (tester) async { + await tester.pumpWidget(MyApp()); + + // 点击添加按钮 + await tester.tap(find.byIcon(Icons.add)); + await tester.pumpAndSettle(); + + // 填写表单 + await tester.enterText(find.byKey(Key('name')), 'Test Server'); + // ... +}); +``` + +## 最佳实践 + +1. **Arrange-Act-Assert**:清晰地组织测试结构(准备-执行-断言) +2. **描述性名称**:测试名称应描述其行为 +3. **每个测试仅一个断言**:保持测试的专注度 +4. **Mock 外部依赖**:不要依赖真实服务器 +5. **测试边缘情况**:处理空列表、空值等 diff --git a/docs/src/content/docs/zh/index.mdx b/docs/src/content/docs/zh/index.mdx new file mode 100644 index 00000000..3a12af7e --- /dev/null +++ b/docs/src/content/docs/zh/index.mdx @@ -0,0 +1,46 @@ +--- +title: Server Box +description: 一款全面的跨平台服务器管理应用 +hero: + tagline: 随时随地管理您的 Linux 服务器 + actions: + - text: 开始使用 + link: /zh/introduction/ + icon: right-arrow + variant: primary + - text: 在 GitHub 上查看 + link: https://github.com/lollipopkit/flutter_server_box + icon: github + variant: minimal +--- + +import { Card, CardGrid } from '@astrojs/starlight/components'; + +## 特性 + + + + 通过精美的实时图表监控 CPU、内存、磁盘、网络、GPU 和温度。 + + + 全功能 SSH 终端,支持多标签页和移动端虚拟键盘。 + + + 使用内置 SFTP 客户端和本地文件浏览器管理服务器上的文件。 + + + 通过直观的界面启动、停止和监控 Docker 和 Podman 容器。 + + + 支持 iOS、Android、macOS、Linux、Windows 和 watchOS。 + + + 完善的本地化支持,包括英语、中文、德语、法语等。 + + + +## 快速链接 + +- **下载**: 可在 [App Store](https://apps.apple.com/app/id1586449703), [GitHub](https://github.com/lollipopkit/flutter_server_box/releases), [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox), [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) 和 [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) 获取 +- **文档**: 探索指南以开始使用 Server Box +- **支持**: 加入我们的 GitHub 社区进行讨论和问题反馈 diff --git a/docs/src/content/docs/zh/installation.mdx b/docs/src/content/docs/zh/installation.mdx new file mode 100644 index 00000000..bf2f6192 --- /dev/null +++ b/docs/src/content/docs/zh/installation.mdx @@ -0,0 +1,53 @@ +--- +title: 安装 +description: 在您的设备上下载并安装 Server Box +--- + +Server Box 适用于多个平台。选择您偏好的安装方式。 + +## 移动应用 + +### iOS + +从 **[App Store](https://apps.apple.com/app/id1586449703)** 下载。 + +### Android + +选择您偏好的来源: + +- **[F-Droid](https://f-droid.org/packages/tech.lolli.toolbox)** - 适合偏好 FOSS 来源的用户 +- **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** - 直接从源获取最新版本 +- **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** - CDN +- **[OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)** - 第三方应用商店 + +## 桌面应用 + +### macOS + +从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 下载。 + +特性: +- 原生菜单栏集成 +- 支持 Intel 和 Apple Silicon + +### Linux + +从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 下载。 + +提供 AppImage、deb 或 tar.gz 格式包。 + +### Windows + +从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 下载。 + +## watchOS + +作为 iOS 应用的一部分,可在 **[App Store](https://apps.apple.com/app/id1586449703)** 上获取。 + +## 从源码构建 + +要从源码构建 Server Box,请参阅开发文档中的[构建](/zh/development/building)部分。 + +## 版本信息 + +查看 [GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases) 页面以获取最新版本和变更日志。 diff --git a/docs/src/content/docs/zh/introduction.mdx b/docs/src/content/docs/zh/introduction.mdx new file mode 100644 index 00000000..9ed341c6 --- /dev/null +++ b/docs/src/content/docs/zh/introduction.mdx @@ -0,0 +1,32 @@ +--- +title: 介绍 +description: 了解 Server Box 是什么以及它能做什么 +--- + +Server Box 是一款使用 Flutter 构建的全面跨平台服务器管理应用。它允许您随时随地监控、管理和控制您的 Linux、Unix 和 Windows 服务器。 + +## 什么是 Server Box? + +Server Box 通过 SSH 连接为服务器管理任务提供统一的界面。无论您是系统管理员、开发人员,还是运行家庭服务器的爱好者,此应用都能为您提供强大的服务器管理工具。 + +## 核心功能 + +- **实时监控**:追踪 CPU、内存、磁盘使用率、网络速度、GPU 状态和系统温度 +- **SSH 终端**:全功能终端访问,支持多标签页和可自定义外观 +- **SFTP 客户端**:浏览和管理服务器上的文件 +- **容器管理**:轻松控制 Docker 和 Podman 容器 +- **进程管理**:查看和管理系统进程 +- **Systemd 服务**:启动、停止和监控 systemd 服务 +- **网络工具**:iPerf 测试、Ping 和网络唤醒 (WoL) +- **代码片段**:保存并执行自定义 Shell 命令 + +## 支持平台 + +Server Box 是真正的跨平台应用: + +- **移动端**:iOS 和 Android +- **桌面端**:macOS、Linux 和 Windows + +## 许可证 + +本项目采用 AGPL v3 许可证。源代码可在 [GitHub](https://github.com/lollipopkit/flutter_server_box) 上获得。 diff --git a/docs/src/content/docs/zh/platforms/desktop.md b/docs/src/content/docs/zh/platforms/desktop.md new file mode 100644 index 00000000..9a91386b --- /dev/null +++ b/docs/src/content/docs/zh/platforms/desktop.md @@ -0,0 +1,74 @@ +--- +title: 桌面端功能 +description: macOS, Linux 和 Windows 的平台特定功能 +--- + +Server Box 在桌面平台上提供了额外的效率功能。 + +## macOS + +### 菜单栏集成 + +- 在菜单栏快速查看服务器状态 +- 一键访问服务器 +- 精简模式,减少干扰 +- 原生 macOS 菜单栏样式 + +### 窗口状态持久化 + +- 记忆窗口位置和大小 +- 启动时恢复上一次会话 +- 支持多显示器 + +### 原生特性 + +- **标题栏**:可选自定义或系统标题栏 +- **全屏模式**:专用的服务器监控视图 +- **键盘快捷键**:macOS 原生快捷键 +- **Touch Bar**(受支持的设备):快速操作 + +## Linux + +### 原生集成 + +- 支持系统托盘 +- 桌面通知集成 +- 文件选择器集成 + +### 窗口管理 + +- 支持 X11 和 Wayland +- 对平铺窗口管理器友好 +- 可选自定义窗口装饰 + +## Windows + +### 功能特性 + +- 系统托盘集成 +- 跳转列表 (Jump List) 快速操作 +- 原生窗口控制 +- 开机自启选项 + +## 跨平台桌面功能 + +### 键盘快捷键 + +- **Cmd/Ctrl + ,**:打开设置 +- **Cmd/Ctrl + Q**:退出应用(仅 macOS) +- **Cmd/Ctrl + 1-9**:切换标签页 + +### 主题 + +- 浅色模式 +- 深色模式 +- AMOLED 模式(纯黑) +- 自动 AMOLED 模式(根据系统设置自动切换) +- 系统模式(跟随系统设置) + +### 桌面端优势 + +- 更大的屏幕用于监控 +- 全尺寸键盘更适合终端操作 +- 更快的文件操作速度 +- 更好的多任务处理 diff --git a/docs/src/content/docs/zh/platforms/mobile.md b/docs/src/content/docs/zh/platforms/mobile.md new file mode 100644 index 00000000..22fed916 --- /dev/null +++ b/docs/src/content/docs/zh/platforms/mobile.md @@ -0,0 +1,77 @@ +--- +title: 移动端功能 +description: iOS 和 Android 的平台特定功能 +--- + +Server Box 为 iOS 和 Android 设备提供了多项移动端特有功能。 + +## 生物识别身份验证 + +使用生物识别技术保护你的服务器安全: + +- **iOS**:Face ID 或 Touch ID +- **Android**:指纹识别 + +在“设置 > 安全 > 生物识别身份验证”中启用。 + +## 主屏幕小组件 + +在主屏幕添加服务器状态小组件,实现快速监控。 + +### iOS + +- 长按主屏幕 +- 点击 **+** 添加小组件 +- 搜索 “Server Box” +- 选择小组件尺寸: + - 小号:单个服务器状态 + - 中号:多个服务器 + - 大号:详细信息 + +### Android + +- 长按主屏幕 +- 点击**小组件** +- 找到 “Server Box” +- 选择小组件类型 + +## 后台运行 + +### Android + +在后台保持连接活跃: + +- 在“设置 > 高级 > 后台运行”中启用 +- 需要排除电池优化 +- 活动连接将显示持久通知 + +### iOS + +受限于后台限制: + +- 后台连接可能会暂停 +- 返回应用时快速重连 +- 支持后台刷新 + +## 推送通知 + +接收以下通知: + +- 服务器离线警报 +- 资源占用过高警告 +- 任务完成提醒 + +在“设置 > 通知”中配置。 + +## 移动端 UI 特性 + +- **下拉刷新**:更新服务器状态 +- **滑动操作**:快速进行服务器操作 +- **横屏模式**:提供更佳的终端体验 +- **虚拟键盘**:提供终端常用快捷键 + +## 文件集成 + +- **文件应用 (iOS)**:通过“文件”应用直接访问 SFTP +- **存储访问框架 (Android)**:与其他应用共享文件 +- **文档选择器**:便捷的文件选择体验 diff --git a/docs/src/content/docs/zh/principles/architecture.md b/docs/src/content/docs/zh/principles/architecture.md new file mode 100644 index 00000000..ce792e46 --- /dev/null +++ b/docs/src/content/docs/zh/principles/architecture.md @@ -0,0 +1,214 @@ +--- +title: 架构概览 +description: 应用程序的高层架构设计 +--- + +Server Box 采用分层架构,实现了清晰的关注点分离。 + +## 架构分层 + +``` +┌─────────────────────────────────────────────────┐ +│ 表现层 (UI) │ +│ lib/view/page/, lib/view/widget/ │ +│ - 页面、组件、控制器 │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ 业务逻辑层 │ +│ lib/data/provider/ │ +│ - Riverpod Provider, State Notifier │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ 数据访问层 │ +│ lib/data/store/, lib/data/model/ │ +│ - Hive 存储, 数据模型 │ +└─────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────┐ +│ 外部集成层 │ +│ - SSH (dartssh2), 终端 (xterm), SFTP │ +│ - 平台特定代码 (iOS, Android 等) │ +└─────────────────────────────────────────────────┘ +``` + +## 应用基础 + +### 入口点 + +`lib/main.dart` 初始化应用: + +```dart +void main() { + runApp( + ProviderScope( + child: MyApp(), + ), + ); +} +``` + +### 根组件 + +`MyApp` 提供以下功能: +- **主题管理**:浅色/深色主题切换 +- **路由配置**:导航结构 +- **Provider Scope**:依赖注入根节点 + +### 首页 + +`HomePage` 作为导航枢纽: +- **标签页界面**:服务器、脚本、容器、SSH +- **状态管理**:各标签页独立状态 +- **导航**:功能入口 + +## 核心系统 + +### 状态管理:Riverpod + +**为何选择 Riverpod?** +- 编译时安全 +- 易于测试 +- 不依赖 Build context +- 跨平台兼容性好 + +**使用的 Provider 类型:** +- `StateProvider`:简单的可变状态 +- `AsyncNotifierProvider`:处理加载/错误/数据状态 +- `StreamProvider`:实时数据流 +- Future providers:一次性异步操作 + +### 数据持久化:Hive CE + +**为何选择 Hive CE?** +- 无原生代码依赖 +- 快速的键值存储 +- 通过代码生成实现类型安全 +- 无需手动添加字段注解 + +**存储类:** +- `SettingStore`:应用偏好设置 +- `ServerStore`:服务器配置 +- `SnippetStore`:命令脚本 +- `KeyStore`:SSH 密钥 + +### 不可变模型:Freezed + +**优势:** +- 编译时不可变性 +- 联合类型处理状态 +- 内置 JSON 序列化 +- CopyWith 扩展 + +## 跨平台策略 + +### 插件系统 + +Flutter 插件提供平台集成: + +| 平台 | 集成方式 | +|----------|-------------------| +| iOS | CocoaPods, Swift/Obj-C | +| Android | Gradle, Kotlin/Java | +| macOS | CocoaPods, Swift | +| Linux | CMake, C++ | +| Windows | CMake, C# | + +### 平台特定功能 + +**仅限 iOS:** +- 主屏幕小组件 +- 实时活动 (Live Activities) +- Apple Watch 配套应用 + +**仅限 Android:** +- 后台服务 +- 推送通知 +- 文件系统访问 + +**仅限桌面端:** +- 菜单栏集成 +- 多窗口支持 +- 自定义标题栏 + +## 自定义依赖 + +### dartssh2 分支 + +增强版 SSH 客户端,具有: +- 更好的移动端支持 +- 增强的错误处理 +- 性能优化 + +### xterm.dart 分支 + +终端模拟器,具有: +- 移动端优化的渲染 +- 手势支持 +- 虚拟键盘集成 + +### fl_lib + +共享工具包,包含: +- 通用组件 +- 扩展方法 +- 辅助函数 + +## 构建系统 + +### fl_build 包 + +自定义构建系统,用于: +- 多平台构建 +- 代码签名 +- 资源打包 +- 版本管理 + +### 构建流程 + +``` +make.dart (版本计算) → fl_build (执行构建) → 平台产物 +``` + +1. **预构建**:从 Git 计算版本号 +2. **构建**:为目标平台编译 +3. **后构建**:打包和签名 + +## 数据流示例 + +### 服务器状态更新 + +``` +1. 定时器触发 → +2. Provider 调用 service → +3. Service 执行 SSH 命令 → +4. 响应解析为模型 → +5. 状态更新 → +6. UI 使用新数据重新构建 +``` + +### 用户操作流 + +``` +1. 用户点击按钮 → +2. Widget 调用 provider 方法 → +3. Provider 更新状态 → +4. 状态更改触发重构 → +5. UI 反映新状态 +``` + +## 安全架构 + +### 数据保护 + +- **密码**:使用 flutter_secure_storage 加密 +- **SSH 密钥**:静态存储时加密 +- **主机指纹**:安全存储 +- **会话数据**:不进行持久化 + +### 连接安全 + +- **主机密钥验证**:检测中间人攻击 +- **加密**:标准 SSH 加密 +- **不存储明文**:敏感数据绝不以明文存储 diff --git a/docs/src/content/docs/zh/principles/sftp.md b/docs/src/content/docs/zh/principles/sftp.md new file mode 100644 index 00000000..6c92e3cd --- /dev/null +++ b/docs/src/content/docs/zh/principles/sftp.md @@ -0,0 +1,490 @@ +--- +title: SFTP 系统 +description: SFTP 文件浏览器的工作原理 +--- + +SFTP 系统通过 SSH 提供文件管理功能。 + +## 架构 + +``` +┌─────────────────────────────────────────────┐ +│ SFTP UI 层 │ +│ - 文件浏览器 (远程) │ +│ - 文件浏览器 (本地) │ +│ - 传输队列 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SFTP 状态管理 │ +│ - sftpProvider │ +│ - 路径管理 │ +│ - 操作队列 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SFTP 协议层 │ +│ - SSH 子系统 │ +│ - 文件操作 │ +│ - 目录列表 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SSH 传输层 │ +│ - 安全通道 │ +│ - 数据流 │ +└─────────────────────────────────────────────┘ +``` + +## 连接建立 + +### 创建 SFTP 客户端 + +```dart +Future createSftpClient(Spi spi) async { + // 1. 获取 SSH 客户端 (如果可用则复用) + final sshClient = await genClient(spi); + + // 2. 打开 SFTP 子系统 + final sftp = await sshClient.openSftp(); + + return sftp; +} +``` + +### 连接复用 + +SFTP 复用现有的 SSH 连接: + +```dart +class ServerProvider { + SSHClient? _sshClient; + SftpClient? _sftpClient; + + Future getSftpClient(String spiId) async { + _sftpClient ??= await _sshClient!.openSftp(); + return _sftpClient!; + } +} +``` + +## 文件系统操作 + +### 目录列表 + +```dart +Future> listDirectory(String path) async { + final sftp = await getSftpClient(spiId); + + // 获取目录列表 + final files = await sftp.listDir(path); + + // 根据设置排序 + 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); + } + }); + + // 如果启用,文件夹优先 + if (showFoldersFirst) { + final dirs = files.where((f) => f.isDirectory); + final regular = files.where((f) => !f.isDirectory); + return [...dirs, ...regular]; + } + + return files; +} +``` + +### 文件元数据 + +```dart +class SftpFile { + final String name; + final String path; + final int size; // 字节 + final int modified; // Unix 时间戳 + final String permissions; // 例如 "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); +} +``` + +## 文件操作 + +### 上传 + +```dart +Future uploadFile( + String localPath, + String remotePath, +) async { + final sftp = await getSftpClient(spiId); + + // 创建请求 + final req = SftpReq( + spi: spi, + remotePath: remotePath, + localPath: localPath, + type: SftpReqType.upload, + ); + + // 添加到队列 + _transferQueue.add(req); + + // 执行带进度的传输 + 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); + }, + ); + + // 完成 + _transferQueue.remove(req); +} +``` + +### 下载 + +```dart +Future downloadFile( + String remotePath, + String localPath, +) async { + final sftp = await getSftpClient(spiId); + + // 创建本地文件 + final file = File(localPath); + final sink = file.openWrite(); + + // 执行带进度的下载 + final stat = await sftp.stat(remotePath); + + await sftp.download( + fromPath: remotePath, + toSink: sink, + onProgress: (transferred) { + _updateProgress( + SftpReq(...), + transferred, + stat.size, + ); + }, + ); + + await sink.close(); +} +``` + +### 权限编辑 + +```dart +Future setPermissions( + String path, + String permissions, +) async { + final sftp = await getSftpClient(spiId); + + // 解析权限 (例如 "rwxr-xr-x" 或 "755") + final mode = parsePermissions(permissions); + + // 通过 SSH 命令设置 (比 SFTP 更可靠) + final ssh = await getSshClient(spiId); + await ssh.exec('chmod $mode "$path"'); +} +``` + +## 路径管理 + +### 路径结构 + +```dart +class PathWithPrefix { + final String prefix; // 例如 "/home/user" + final String path; // 相对或绝对路径 + + String get fullPath { + if (path.startsWith('/')) { + return path; // 绝对路径 + } + return '$prefix/$path'; // 相对路径 + } + + PathWithPrefix cd(String subPath) { + return PathWithPrefix( + prefix: fullPath, + path: subPath, + ); + } +} +``` + +### 导航历史 + +```dart +class PathHistory { + final List _history = []; + int _index = -1; + + void push(String path) { + // 移除前进历史 + _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; + } +} +``` + +## 传输系统 + +### 传输请求 + +```dart +class SftpReq { + final Spi spi; + final String remotePath; + final String localPath; + final SftpReqType type; + final DateTime createdAt; + + int? totalBytes; + int? transferredBytes; + String? error; +} +``` + +### 进度跟踪 + +```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); + } +} +``` + +### 队列管理 + +```dart +class TransferQueue { + final List _queue = []; + final Map _progress = {}; + int _concurrent = 3; // 最大并发传输数 + + Future 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 _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); + } + } +} +``` + +## 本地存储模式 + +### 下载缓存 + +下载的文件存储在: + +```dart +String getLocalDownloadPath(String spiId, String remotePath) { + final normalized = remotePath.replaceAll('/', '_'); + return 'Paths.file/$spiId/$normalized'; +} +``` + +示例: +- 远程:`/var/log/nginx/access.log` +- spiId:`server-123` +- 本地:`Paths.file/server-123/_var_log_nginx_access.log` + +## 文件编辑 + +### 编辑工作流 + +```dart +Future editFile(String path) async { + final sftp = await getSftpClient(spiId); + + // 1. 检查大小 + final stat = await sftp.stat(path); + if (stat.size > editorMaxSize) { + showWarning('文件太大,内置编辑器无法打开'); + return; + } + + // 2. 下载到临时目录 + final temp = await downloadToTemp(path); + + // 3. 在编辑器中打开 + final content = await openEditor(temp.path); + + // 4. 上传回服务器 + await uploadFile(temp.path, path); + + // 5. 清理 + await temp.delete(); +} +``` + +### 外部编辑器集成 + +```dart +Future editInExternalEditor(String path) async { + final ssh = await getSshClient(spiId); + + // 使用编辑器打开终端 + final editor = getSetting('sftpEditor', 'vim'); + await ssh.exec('$editor "$path"'); + + // 用户在终端中编辑 + // 保存后,刷新 SFTP 视图 +} +``` + +## 错误处理 + +### 权限错误 + +```dart +try { + await sftp.upload(...); +} on SftpPermissionException { + showError('拒绝访问:${stat.path}'); + showHint('请检查文件权限和所有权'); +} +``` + +### 连接错误 + +```dart +try { + await sftp.listDir(path); +} on SftpConnectionException { + showError('连接丢失'); + await reconnect(); +} +``` + +### 空间错误 + +```dart +try { + await sftp.upload(...); +} on SftpNoSpaceException { + showError('远程服务器磁盘空间不足'); +} +``` + +## 性能优化 + +### 目录缓存 + +```dart +class DirectoryCache { + final Map _cache = {}; + final Duration ttl = Duration(minutes: 5); + + Future> 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; + } +} +``` + +### 懒加载 + +对于大型目录(>1000 个项目): + +```dart +List 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)); +} +``` + +### 分页 + +```dart +class PaginatedDirectory { + static const pageSize = 100; + + Future> getPage(int page) async { + final offset = page * pageSize; + return await sftp.listDir( + path, + offset: offset, + limit: pageSize, + ); + } +} +``` diff --git a/docs/src/content/docs/zh/principles/ssh.md b/docs/src/content/docs/zh/principles/ssh.md new file mode 100644 index 00000000..e19aa7ba --- /dev/null +++ b/docs/src/content/docs/zh/principles/ssh.md @@ -0,0 +1,305 @@ +--- +title: SSH 连接 +description: SSH 连接是如何建立和管理的 +--- + +了解 Server Box 中的 SSH 连接机制。 + +## 连接流程 + +```text +用户输入 → Spi 配置 → genClient() → SSH 客户端 → 会话 (Session) +``` + +### 第一步:配置 + +`Spi` (Server Parameter Info) 模型包含: + +```dart +class Spi { + String id; // 唯一标识 + String name; // 服务器名称 + String ip; // IP 地址 + int port; // SSH 端口 (默认 22) + String user; // 用户名 + String? pwd; // 密码 (加密存储) + String? keyId; // SSH 密钥 ID + String? jumpId; // 跳板机 ID + String? alterUrl; // 备用 URL +} +``` + +### 第二步:生成客户端 + +`genClient(spi)` 创建 SSH 客户端: + +```dart +Future genClient(Spi spi) async { + // 1. 建立 socket + var socket = await connect(spi.ip, spi.port); + + // 2. 如果失败,尝试备用 URL + if (socket == null && spi.alterUrl != null) { + socket = await connect(spi.alterUrl, spi.port); + } + + if (socket == null) { + throw ConnectionException('Unable to connect'); + } + + // 3. 身份验证 + final client = SSHClient( + socket: socket, + username: spi.user, + onPasswordRequest: () => spi.pwd, + onIdentityRequest: () => loadKey(spi.keyId), + ); + + // 4. 验证主机密钥 + await verifyHostKey(client, spi); + + return client; +} +``` + +### 第三步:跳板机 (如果已配置) + +对于跳板机,采用递归连接: + +```dart +if (spi.jumpId != null) { + final jumpClient = await genClient(getJumpSpi(spi.jumpId)); + final forwarded = await jumpClient.forwardLocal( + spi.ip, + spi.port, + ); + // 通过转发的 socket 进行连接 +} +``` + +## 身份验证方式 + +### 密码验证 + +```dart +onPasswordRequest: () => spi.pwd +``` + +- 密码以加密形式存储在 Hive 中 +- 连接时解密 +- 发送到服务器进行验证 + +### 私钥验证 + +```dart +onIdentityRequest: () async { + final key = await KeyStore.get(spi.keyId); + return decyptPem(key.pem, key.password); +} +``` + +**密钥加载流程:** +1. 从 `KeyStore` 获取加密的密钥 +2. 解密密码(通过生物识别或提示) +3. 解析 PEM 格式 +4. 标准化换行符 (LF) +5. 返回用于身份验证 + +### 键盘交互式 (Keyboard-Interactive) + +```dart +onUserInfoRequest: (instructions) async { + // 处理挑战-响应 (Challenge-Response) + return responses; +} +``` + +支持: +- 密码验证 +- OTP 令牌 +- 双因子认证 (2FA) + +## 主机密钥验证 + +### 为什么要验证主机密钥? + +通过确保连接的是同一个服务器,防止**中间人 (MITM)** 攻击。 + +### 存储格式 + +```text +{spi.id}::{keyType} +``` + +示例: +```text +my-server::ssh-ed25519 +my-server::ecdsa-sha2-nistp256 +``` + +### 指纹格式 + +**MD5 十六进制:** +```text +aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99 +``` + +**Base64:** +```text +SHA256:AbCdEf1234567890...= +``` + +### 验证流程 + +```dart +Future verifyHostKey(SSHClient client, Spi spi) async { + final key = await client.hostKey; + final keyType = key.type; + final fingerprint = md5Hex(key); // 或 base64 + + final stored = SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType']; + + if (stored == null) { + // 新主机 - 提示用户 + final trust = await promptUser( + '未知主机', + '指纹: $fingerprint', + ); + if (trust) { + SettingStore.sshKnownHostsFingerprints + ['${spi.id}::$keyType'] = fingerprint; + } + } else if (stored != fingerprint) { + // 已更改 - 警告用户 + await warnUser( + '主机密钥已更改!', + '可能存在中间人攻击', + ); + } +} +``` + +## 会话管理 + +### 连接池 + +在 `ServerProvider` 中维护活动的客户端: + +```dart +class ServerProvider { + final Map _clients = {}; + + SSHClient getClient(String spiId) { + return _clients[spiId] ??= connect(spiId); + } +} +``` + +### 心跳检测 (Keep-Alive) + +在闲置期间维持连接: + +```dart +Timer.periodic( + Duration(seconds: 30), + (_) => client.sendKeepAlive(), +); +``` + +### 自动重连 + +连接丢失时: + +```dart +client.onError.listen((error) async { + await Future.delayed(Duration(seconds: 5)); + reconnect(); +}); +``` + +## 连接生命周期 + +```text +┌─────────────┐ +│ 初始化 │ +└──────┬──────┘ + │ connect() + ↓ +┌─────────────┐ +│ 连接中 │ ←──┐ +└──────┬──────┘ │ + │ 成功 │ + ↓ │ 失败 (重试) +┌─────────────┐ │ +│ 已连接 │───┘ +└──────┬──────┘ + │ + ↓ +┌─────────────┐ +│ 活跃中 │ ──→ 发送命令 +└──────┬──────┘ + │ + ↓ (错误/断开) +┌─────────────┐ +│ 已断开 │ +└─────────────┘ +``` + +## 错误处理 + +### 连接超时 + +```dart +try { + await client.connect().timeout( + Duration(seconds: 30), + ); +} on TimeoutException { + throw ConnectionException('连接超时'); +} +``` + +### 身份验证失败 + +```dart +onAuthFail: (error) { + if (error.contains('password')) { + return '密码无效'; + } else if (error.contains('key')) { + return 'SSH 密钥无效'; + } + return '身份验证失败'; +} +``` + +### 主机密钥不匹配 + +```dart +onHostKeyMismatch: (stored, current) { + showSecurityWarning( + '主机密钥已更改!', + '可能存在中间人攻击', + ); +} +``` + +## 性能考量 + +### 连接复用 + +- 在不同功能间复用客户端 +- 避免不必要的断开和重连 +- 为并发操作建立连接池 + +### 最佳设置 + +- **超时时间**:30 秒 (可调) +- **心跳频率**:每 30 秒一次 +- **重试延迟**:5 秒 + +### 网络效率 + +- 单个连接处理多个操作 +- 尽可能使用管道 (Pipeline) 命令 +- 避免打开多个连接 diff --git a/docs/src/content/docs/zh/principles/state.md b/docs/src/content/docs/zh/principles/state.md new file mode 100644 index 00000000..e73f3d77 --- /dev/null +++ b/docs/src/content/docs/zh/principles/state.md @@ -0,0 +1,301 @@ +--- +title: 状态管理 +description: 如何使用 Riverpod 进行状态管理 +--- + +了解 Server Box 中的状态管理架构。 + +## 为何选择 Riverpod? + +**主要优势:** +- **编译时安全**:在编译阶段即可发现错误 +- **无需 BuildContext**:可在任何地方访问状态 +- **易于测试**:方便对 Provider 进行隔离测试 +- **代码生成**:减少样板代码,确保类型安全 + +## Provider 架构 + +``` +┌─────────────────────────────────────────────┐ +│ UI 层 (Widgets) │ +│ - ConsumerWidget / ConsumerStatefulWidget │ +│ - ref.watch() / ref.read() │ +└─────────────────────────────────────────────┘ + ↓ 监听 (watches) +┌─────────────────────────────────────────────┐ +│ Provider 层 │ +│ - @riverpod 注解 │ +│ - 生成的 *.g.dart 文件 │ +└─────────────────────────────────────────────┘ + ↓ 使用 (uses) +┌─────────────────────────────────────────────┐ +│ Service / Store 层 │ +│ - 业务逻辑 │ +│ - 数据访问 │ +└─────────────────────────────────────────────┘ +``` + +## 使用的 Provider 类型 + +### 1. StateProvider (简单状态) + +用于简单的可观察状态: + +```dart +@riverpod +class ThemeNotifier extends _$ThemeNotifier { + @override + ThemeMode build() { + // 从设置中加载 + return SettingStore.themeMode; + } + + void setTheme(ThemeMode mode) { + state = mode; + SettingStore.themeMode = mode; // 持久化存储 + } +} +``` + +**使用示例:** +```dart +class MyWidget extends ConsumerWidget { + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = ref.watch(themeNotifierProvider); + return Text('当前主题:$theme'); + } +} +``` + +### 2. AsyncNotifierProvider (异步状态) + +用于异步加载的数据: + +```dart +@riverpod +class ServerStatus extends _$ServerStatus { + @override + Future build(Server server) async { + // 初始加载 + return await fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() async { + return await fetchStatus(server); + }); + } +} +``` + +**使用示例:** +```dart +final status = ref.watch(serverStatusProvider(server)); + +status.when( + data: (data) => StatusWidget(data), + loading: () => LoadingWidget(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 3. StreamProvider (实时数据) + +用于持续的数据流: + +```dart +@riverpod +Stream cpuUsage(CpuUsageRef ref, Server server) { + final client = ref.watch(sshClientProvider(server)); + final stream = client.monitorCpu(); + + // 当不再被监听时自动释放资源 + ref.onDispose(() { + client.stopMonitoring(); + }); + + return stream; +} +``` + +**使用示例:** +```dart +final cpu = ref.watch(cpuUsageProvider(server)); + +cpu.when( + data: (usage) => CpuChart(usage), + loading: () => CircularProgressIndicator(), + error: (error, stack) => ErrorWidget(error), +) +``` + +### 4. Family Provider (带参数) + +可以接收参数的 Provider: + +```dart +@riverpod +Future> containers(ContainersRef ref, Server server) async { + final client = await ref.watch(sshClientProvider(server).future); + return await client.listContainers(); +} +``` + +**使用示例:** +```dart +final containers = ref.watch(containersProvider(server)); + +// 不同的服务器对应不同的缓存状态 +final containers2 = ref.watch(containersProvider(server2)); +``` + +## 状态更新模式 + +### 直接更新状态 + +```dart +ref.read(settingsProvider.notifier).updateTheme(darkMode); +``` + +### 计算状态 (Computed State) + +```dart +@riverpod +int totalServers(TotalServersRef ref) { + final servers = ref.watch(serversProvider); + return servers.length; +} +``` + +### 派生状态 (Derived State) + +```dart +@riverpod +List onlineServers(OnlineServersRef ref) { + final all = ref.watch(serversProvider); + return all.where((s) => s.isOnline).toList(); +} +``` + +## 服务器特定状态 + +### 单服务器 Provider + +每个服务器都有独立的状态: + +```dart +@riverpod +class ServerProvider extends _$ServerProvider { + @override + ServerState build(Server server) { + return ServerState.disconnected(); + } + + Future connect() async { + state = ServerState.connecting(); + try { + final client = await genClient(server.spi); + state = ServerState.connected(client); + } catch (e) { + state = ServerState.error(e.toString()); + } + } +} +``` + +### Provider 键 (Keys) + +```dart +// 每个服务器都有唯一的 Provider +@riverpod +ServerStatus serverStatus(ServerStatusRef ref, Server server) { + // server.id 被用作 key +} +``` + +## 响应式模式 + +### 自动刷新 + +```dart +@riverpod +class AutoRefreshServerStatus extends _$AutoRefreshServerStatus { + Timer? _timer; + + @override + Future build(Server server) async { + // 启动定时器 + _timer = Timer.periodic(Duration(seconds: 5), (_) { + refresh(); + }); + + ref.onDispose(() { + _timer?.cancel(); + }); + + return await fetchStatus(server); + } + + Future refresh() async { + state = const AsyncValue.loading(); + state = await AsyncValue.guard(() => fetchStatus(server)); + } +} +``` + +### 多 Provider 依赖 + +```dart +@riverpod +Future systemInfo(SystemInfoRef ref, Server server) async { + // 先等待 SSH 客户端建立连接 + final client = await ref.watch(sshClientProvider(server).future); + + // 然后获取系统信息 + return await client.getSystemInfo(); +} +``` + +## 状态持久化 + +### Hive 集成 + +```dart +@riverpod +class ServerStoreNotifier extends _$ServerStoreNotifier { + @override + List build() { + // 从 Hive 加载 + return Hive.box('servers').values.toList(); + } + + void addServer(Server server) { + state = [...state, server]; + // 持久化到 Hive + Hive.box('servers').put(server.id, server); + } + + void removeServer(String id) { + state = state.where((s) => s.id != id).toList(); + // 从 Hive 中删除 + Hive.box('servers').delete(id); + } +} +``` + +## 性能优化 + +- **Provider Keep-Alive**:通过 `@Riverpod(keepAlive: true)` 防止无监听者时自动销毁 +- **选择性监听**:使用 `select` 仅监听状态的特定部分 +- **Provider 缓存**:Family Provider 为每个参数缓存结果 + +## 最佳实践 + +1. **就近放置 Provider**:放在消费它的 Widget 附近 +2. **使用代码生成**:始终使用 `@riverpod` 注解 +3. **保持 Provider 专注**:遵循单一职责原则 +4. **处理加载状态**:务必处理 AsyncValue 的各种状态 +5. **及时销毁资源**:在 `ref.onDispose()` 中进行清理 +6. **避免过深的 Provider 树**:保持 Provider 图结构扁平 diff --git a/docs/src/content/docs/zh/principles/terminal.md b/docs/src/content/docs/zh/principles/terminal.md new file mode 100644 index 00000000..81f11d6b --- /dev/null +++ b/docs/src/content/docs/zh/principles/terminal.md @@ -0,0 +1,248 @@ +--- +title: 终端实现 +description: SSH 终端的内部工作原理 +--- + +SSH 终端是功能最复杂的模块之一,基于自定义的 xterm.dart 分支构建。 + +## 架构概览 + +``` +┌─────────────────────────────────────────────┐ +│ 终端 UI 层 │ +│ - 标签页管理 │ +│ - 虚拟键盘 │ +│ - 文本选择 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ xterm.dart 模拟器 │ +│ - PTY (伪终端) │ +│ - VT100/ANSI 模拟 │ +│ - 渲染引擎 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ SSH 客户端层 │ +│ - SSH 会话 │ +│ - 通道管理 │ +│ - 数据流 │ +└─────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────┐ +│ 远程服务器 │ +│ - Shell 进程 │ +│ - 命令执行 │ +└─────────────────────────────────────────────┘ +``` + +## 终端会话生命周期 + +### 1. 创建会话 + +```dart +Future createSession(Spi spi) async { + // 1. 获取 SSH 客户端 + final client = await genClient(spi); + + // 2. 创建 PTY + final pty = await client.openPty( + term: 'xterm-256color', + cols: 80, + rows: 24, + ); + + // 3. 初始化终端模拟器 + final terminal = Terminal( + backend: PtyBackend(pty), + ); + + // 4. 设置调整尺寸处理程序 + terminal.onResize.listen((size) { + pty.resize(size.cols, size.rows); + }); + + return TerminalSession( + terminal: terminal, + pty: pty, + client: client, + ); +} +``` + +### 2. 终端模拟 + +xterm.dart 分支提供: + +**VT100/ANSI 模拟:** +- 光标移动 +- 颜色(支持 256 色) +- 文本属性(粗体、下划线等) +- 滚动区域 +- 交替屏幕缓冲区 + +**渲染:** +- 基于行的渲染 +- 双向文本支持 +- Unicode/Emoji 支持 +- 优化重绘 + +### 3. 数据流向 + +``` +用户输入 + ↓ +虚拟键盘 / 实体键盘 + ↓ +终端模拟器 (按键 → 转义序列) + ↓ +SSH 通道 (发送) + ↓ +远程 PTY + ↓ +远程 Shell + ↓ +命令输出 + ↓ +SSH 通道 (接收) + ↓ +终端模拟器 (解析 ANSI 编码) + ↓ +渲染到屏幕 +``` + +## 多标签页系统 + +### 标签页管理 + +```dart +class TerminalTabs { + final Map _tabs = {}; + String? _activeTabId; + + void createTab(Server server) { + final id = _generateTabId(server); + _tabs[id] = TabData( + id: id, + name: _generateTabName(server), + session: createSession(server), + ); + _activeTabId = id; + } + + String _generateTabName(Server server) { + final count = _tabs.values + .where((t) => t.name.startsWith(server.name)) + .length; + return count == 0 ? server.name : '${server.name}($count)'; + } +} +``` + +### 会话持久化 + +标签页在导航切换时会保持状态: +- SSH 连接保持活跃 +- 终端状态保留 +- 滚动缓冲区保留 +- 输入历史保留 + +## 虚拟键盘 + +### 平台特定实现 + +**iOS:** +- 基于 UIView 的自定义键盘 +- 可通过键盘按钮切换 +- 根据焦点自动显示/隐藏 + +**Android:** +- 自定义输入法 +- 与系统键盘集成 +- 快速操作按钮 + +### 键盘按键 + +| 按钮 | 操作 | +|--------|--------| +| **切换 (Toggle)** | 显示/隐藏系统键盘 | +| **Ctrl** | 发送 Ctrl 修饰符 | +| **Alt** | 发送 Alt 修饰符 | +| **SFTP** | 打开当前目录 | +| **剪贴板 (Clipboard)** | 上下文感知的复制/粘贴 | +| **脚本 (Snippets)** | 执行命令脚本 | + +## 文本选择 + +1. **长按**:进入选择模式 +2. **拖动**:扩大选择范围 +3. **释放**:复制到剪贴板 + +## 字体与尺寸 + +### 尺寸计算 + +```dart +class TerminalDimensions { + static Size calculate(double fontSize, Size screenSize) { + final charWidth = fontSize * 0.6; // 等宽字体宽高比 + final charHeight = fontSize * 1.2; + + final cols = (screenSize.width / charWidth).floor(); + final rows = (screenSize.height / charHeight).floor(); + + return Size(cols.toDouble(), rows.toDouble()); + } +} +``` + +### 捏合缩放 (Pinch-to-Zoom) + +```dart +GestureDetector( + onScaleStart: () => _baseFontSize = currentFontSize, + onScaleUpdate: (details) { + final newFontSize = _baseFontSize * details.scale; + resize(newFontSize); + }, +) +``` + +## 配色方案 + +- **浅色 (Light)**:浅色背景,深色文字 +- **深色 (Dark)**:深色背景,浅色文字 +- **AMOLED**:纯黑背景 + +## 性能优化 + +- **脏矩形 (Dirty rectangle)**:仅重绘更改的区域 +- **行缓存 (Line caching)**:缓存渲染的行 +- **延迟滚动 (Lazy scrolling)**:长缓冲区的虚拟滚动 +- **批量更新**:合并多次写入 +- **压缩**:压缩滚动缓冲区内容 +- **防抖 (Debouncing)**:对快速输入进行防抖处理 + +## 特色功能 + +### 脚本执行 + +```dart +void executeSnippet(Snippet snippet) { + final formatted = formatSnippet(snippet); + terminal.paste(formatted); + terminal.paste('\r'); // 执行 +} +``` + +### SFTP 快速访问 + +```dart +void openSftp() async { + final cwd = await terminal.getCurrentWorkingDirectory(); + Navigator.push( + context, + SftpPage(initialPath: cwd), + ); +} +``` diff --git a/docs/src/content/docs/zh/quick-start.mdx b/docs/src/content/docs/zh/quick-start.mdx new file mode 100644 index 00000000..aa787f2b --- /dev/null +++ b/docs/src/content/docs/zh/quick-start.mdx @@ -0,0 +1,45 @@ +--- +title: 快速开始 +description: 在几分钟内开始使用 Server Box +--- + +按照本快速入门指南连接您的第一台服务器并开始监控。 + +## 第一步:添加服务器 + +1. 打开 Server Box +2. 点击 **+** 按钮添加新服务器 +3. 填写服务器信息: + - **名称 (Name)**:服务器的友好名称 + - **主机 (Host)**:IP 地址或域名 + - **端口 (Port)**:SSH 端口(默认:22) + - **用户 (User)**:SSH 用户名 + - **密码或密钥 (Password or Key)**:身份验证方式 + +4. 点击 **保存 (Save)** 添加服务器 + +## 第二步:连接并监控 + +1. 点击服务器卡片进行连接 +2. 应用程序将建立 SSH 连接 +3. 您将看到以下内容的实时状态: + - CPU 使用率 + - 内存 (RAM) 和交换分区 (Swap) + - 磁盘使用情况 + - 网络速度 + +## 第三步:探索功能 + +连接后,您可以: + +- **打开终端**:点击终端按钮以获得完整的 SSH 访问权限 +- **浏览文件**:使用 SFTP 管理文件 +- **管理容器**:查看和控制 Docker/Podman 容器 +- **查看进程**:检查正在运行的进程 +- **运行片段**:执行保存的命令 + +## 提示 + +- **生物识别身份验证**:启用 Face ID / Touch ID / 指纹以快速访问(移动端) +- **主屏幕小组件**:将服务器状态小组件添加到您的主屏幕(iOS/Android) +- **后台运行**:在后台保持连接(Android) diff --git a/docs/src/styles/custom.css b/docs/src/styles/custom.css index ace1f804..821c84a3 100644 --- a/docs/src/styles/custom.css +++ b/docs/src/styles/custom.css @@ -1,4 +1,4 @@ -/* Flutter Server Box Custom Styles */ +/* Server Box Custom Styles */ :root { --sl-color-accent: #02569b;