diff --git a/Makefile b/Makefile index fb6a25caa..e82fe6bd9 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,13 @@ PLATFORM ?= ENV_FILE ?= XCARCHIVE_PATH ?= APP_PATH ?= +DMG_PATH ?= +TAP_REPO_PATH ?= .PHONY: help deps pub-get run run-device analyze test test-one coverage \ gen gen-build gen-build-clean gen-l10n build build-android build-ios \ - build-macos build-linux build-windows clean release-macos-dmg package-dmg + build-macos build-linux build-windows clean release-macos-dmg package-dmg \ + sync-homebrew-cask help: @printf '%s\n' \ @@ -48,7 +51,11 @@ help: ' Optional: make release-macos-dmg ENV_FILE=.env.release' \ ' package-dmg Run scripts/release/package-dmg-from-xcarchive.sh' \ ' Example: make package-dmg APP_PATH="/path/Server Box.app"' \ - ' Example: make package-dmg XCARCHIVE_PATH=/path/Runner.xcarchive' + ' Example: make package-dmg XCARCHIVE_PATH=/path/Runner.xcarchive' \ + ' sync-homebrew-cask Generate ~/proj/homebrew-taps/Casks/server-box.rb from a built DMG' \ + ' Example: make sync-homebrew-cask APP_PATH="/path/Server Box.app"' \ + ' Example: make sync-homebrew-cask XCARCHIVE_PATH=/path/Runner.xcarchive' \ + ' Example: make sync-homebrew-cask DMG_PATH=build/artifacts/ServerBox-1.0.1.dmg' deps pub-get: $(FLUTTER) pub get @@ -134,3 +141,19 @@ package-dmg: else \ XCARCHIVE_PATH="$(XCARCHIVE_PATH)" bash scripts/release/package-dmg-from-xcarchive.sh; \ fi + +sync-homebrew-cask: + @if [ -z "$(APP_PATH)" ] && [ -z "$(XCARCHIVE_PATH)" ] && [ -z "$(DMG_PATH)" ]; then \ + echo 'APP_PATH, XCARCHIVE_PATH, or DMG_PATH is required.'; \ + echo 'Example: make sync-homebrew-cask APP_PATH="/path/Server Box.app"'; \ + echo 'Example: make sync-homebrew-cask XCARCHIVE_PATH=/path/Runner.xcarchive'; \ + echo 'Example: make sync-homebrew-cask DMG_PATH=build/artifacts/ServerBox-1.0.1.dmg'; \ + exit 1; \ + fi + @if [ -n "$(APP_PATH)" ]; then \ + APP_PATH="$(APP_PATH)" DMG_PATH="$(DMG_PATH)" TAP_REPO_PATH="$(TAP_REPO_PATH)" bash scripts/release/sync-homebrew-cask.sh; \ + elif [ -n "$(XCARCHIVE_PATH)" ]; then \ + XCARCHIVE_PATH="$(XCARCHIVE_PATH)" DMG_PATH="$(DMG_PATH)" TAP_REPO_PATH="$(TAP_REPO_PATH)" bash scripts/release/sync-homebrew-cask.sh; \ + else \ + DMG_PATH="$(DMG_PATH)" TAP_REPO_PATH="$(TAP_REPO_PATH)" bash scripts/release/sync-homebrew-cask.sh; \ + fi diff --git a/README.md b/README.md index 43192bede..c9dd70e9b 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,10 @@ Especially thanks to dartss |Platform| From| |--|--| -| iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703) | -| Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) | -| Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) | +| iOS | [AppStore](https://apps.apple.com/app/id1586449703) | +| macOS | [AppStore](https://apps.apple.com/app/id1586449703) / brew install --cask server-box | +| Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) | +| Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) | Please only download pkgs from the source that **you trust**! @@ -73,17 +74,6 @@ If I forgot to add your name to the contributors list, please add a comment in t 2. Clone this repo, run `flutter run` to start the app. 3. Run `dart run fl_build -p PLATFORM` to build the app. -### Release macOS notarized DMG - -1. Copy `.env.release.example` to `.env.release`. -2. Fill in `APPLE_TEAM_ID` and `APPLE_NOTARY_KEYCHAIN_PROFILE`. -3. Make sure the `Developer ID Application` certificate is already installed in Keychain. -4. Make sure notarization credentials are already stored via `xcrun notarytool store-credentials`. -5. Install the provisioning profile used for DMG packaging. The script defaults to `ServerBox DMG Profile`, and you can override it with `APP_PROFILE_NAME`. -6. Run `bash scripts/release/release-macos-dmg.sh`. - -This flow does not modify the default Xcode Release signing config. It injects a temporary `xcconfig` only for archive/export, builds a signed `.app`, packages a DMG, submits it to notarization, staples the result, and optionally uploads the DMG to the GitHub Release for `v`. - ### Translation - [Guide](https://blog.lpkt.cn/posts/faq/) can be found in my blog. diff --git a/README_zh.md b/README_zh.md index e2659811b..9948efe81 100644 --- a/README_zh.md +++ b/README_zh.md @@ -30,8 +30,9 @@ 平台|下载 --|-- -iOS / macOS | [AppStore](https://apps.apple.com/app/id1586449703) -Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) +| iOS | [AppStore](https://apps.apple.com/app/id1586449703) | +| macOS | [AppStore](https://apps.apple.com/app/id1586449703) / brew install --cask server-box | +Android | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) / [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox) / [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/releases) / [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) 请从 **信任** 的来源下载! @@ -75,17 +76,6 @@ Linux / Windows | [GitHub](https://github.com/lollipopkit/flutter_server_box/rel 2. 克隆这个仓库, 运行 `flutter run` 启动应用 3. 运行 `dart run fl_build -p PLATFORM` 构建应用 -### 发布 macOS 公证 DMG - -1. 复制 `.env.release.example` 为 `.env.release` -2. 填入 `APPLE_TEAM_ID` 和 `APPLE_NOTARY_KEYCHAIN_PROFILE` -3. 确保 `Developer ID Application` 证书已经安装到 Keychain -4. 确保已经通过 `xcrun notarytool store-credentials` 存好了公证凭据 -5. 安装用于 DMG 打包的 provisioning profile。脚本默认使用 `ServerBox DMG Profile`,也可以通过 `APP_PROFILE_NAME` 覆盖 -6. 运行 `bash scripts/release/release-macos-dmg.sh` - -这套流程不会修改工程默认的 Xcode Release 签名配置。脚本只会在归档和导出时注入临时 `xcconfig`,生成已签名 `.app`、打包 DMG、提交公证、回填 stapler,并可选把 DMG 上传到 `v` 对应的 GitHub Release。 - ### 翻译 [指南](https://blog.lpkt.cn/faq/) 可在我的博客中找到。 diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 6b09adaef..b8adc313e 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -4,6 +4,7 @@ import starlight from '@astrojs/starlight'; // https://astro.build/config export default defineConfig({ + base: '/docs', integrations: [ starlight({ title: 'Server Box', diff --git a/docs/src/content/docs/advanced/bulk-import.md b/docs/src/content/docs/advanced/bulk-import.md index 8ba04647e..b59e2f9c0 100644 --- a/docs/src/content/docs/advanced/bulk-import.md +++ b/docs/src/content/docs/advanced/bulk-import.md @@ -10,7 +10,7 @@ Import multiple server configurations at once using a JSON file. :::danger[Security Warning] **Never store plaintext passwords in files!** This JSON example shows a password field for demonstration only, but you should: -- **Prefer SSH keys** (`keyId`) instead of `pwd` - they're more secure +- **Prefer SSH keys** (`pubKeyId`) instead of `pwd` - they're more secure - **Use secret managers** or environment variables if you must use passwords - **Delete the file immediately** after import - don't leave credentials lying around - **Add to .gitignore** - never commit credential files to version control @@ -24,7 +24,7 @@ Import multiple server configurations at once using a JSON file. "port": 22, "user": "root", "pwd": "password", - "keyId": "", + "pubKeyId": "", "tags": ["production"], "autoConnect": false } @@ -40,9 +40,10 @@ Import multiple server configurations at once using a JSON file. | `port` | Yes | SSH port (usually 22) | | `user` | Yes | SSH username | | `pwd` | No | Password (avoid - use SSH keys instead) | -| `keyId` | No | SSH key name (from Private Keys - recommended) | +| `pubKeyId` | No | Private key id (from Private Keys - recommended) | | `tags` | No | Organization tags | | `autoConnect` | No | Auto-connect on startup | +| `id` | No | Stable server id; omitted or empty values are generated on import | ## Import Steps @@ -60,7 +61,7 @@ Import multiple server configurations at once using a JSON file. "ip": "prod.example.com", "port": 22, "user": "admin", - "keyId": "my-key", + "pubKeyId": "my-key", "tags": ["production", "web"] }, { @@ -68,7 +69,7 @@ Import multiple server configurations at once using a JSON file. "ip": "dev.example.com", "port": 2222, "user": "dev", - "keyId": "dev-key", + "pubKeyId": "dev-key", "tags": ["development"] } ] diff --git a/docs/src/content/docs/de/advanced/bulk-import.md b/docs/src/content/docs/de/advanced/bulk-import.md index d603698ae..1a1f29071 100644 --- a/docs/src/content/docs/de/advanced/bulk-import.md +++ b/docs/src/content/docs/de/advanced/bulk-import.md @@ -10,7 +10,7 @@ Importieren Sie mehrere Serverkonfigurationen gleichzeitig mithilfe einer JSON-D :::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 +- **SSH-Schlüssel bevorzugen** (`pubKeyId`) anstelle von `pwd` - diese sind sicherer - **Passwort-Manager** oder Umgebungsvariablen verwenden, wenn Sie Passwörter verwenden müssen - **Löschen Sie die Datei sofort** nach dem Import - lassen Sie keine Anmeldedaten herumliegen - **Fügen Sie sie zur .gitignore hinzu** - checken Sie niemals Anmeldedatendateien in die Versionsverwaltung ein @@ -24,7 +24,7 @@ Importieren Sie mehrere Serverkonfigurationen gleichzeitig mithilfe einer JSON-D "port": 22, "user": "root", "pwd": "password", - "keyId": "", + "pubKeyId": "", "tags": ["production"], "autoConnect": false } @@ -40,7 +40,7 @@ Importieren Sie mehrere Serverkonfigurationen gleichzeitig mithilfe einer JSON-D | `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) | +| `pubKeyId` | Nein | Private-Key-ID (aus Private Keys - empfohlen) | | `tags` | Nein | Organisations-Tags | | `autoConnect` | Nein | Automatische Verbindung beim Start | @@ -60,7 +60,7 @@ Importieren Sie mehrere Serverkonfigurationen gleichzeitig mithilfe einer JSON-D "ip": "prod.example.com", "port": 22, "user": "admin", - "keyId": "my-key", + "pubKeyId": "my-key", "tags": ["production", "web"] }, { @@ -68,7 +68,7 @@ Importieren Sie mehrere Serverkonfigurationen gleichzeitig mithilfe einer JSON-D "ip": "dev.example.com", "port": 2222, "user": "dev", - "keyId": "dev-key", + "pubKeyId": "dev-key", "tags": ["development"] } ] diff --git a/docs/src/content/docs/de/development/architecture.md b/docs/src/content/docs/de/development/architecture.md index 4e0a48412..f97307dad 100644 --- a/docs/src/content/docs/de/development/architecture.md +++ b/docs/src/content/docs/de/development/architecture.md @@ -47,7 +47,7 @@ Server Box folgt den Prinzipien der Clean Architecture mit einer klaren Trennung ### Lokale Speicherung: Hive - **hive_ce**: Community-Edition von Hive -- Keine manuellen `@HiveField` oder `@HiveType` erforderlich +- Folgen Sie dem bestehenden Modellmuster: Die meisten Stores verwenden `hive_ce`, einige verfolgte Modelle deklarieren weiterhin explizit `@HiveType` und `@HiveField` - Typ-Adapter werden automatisch generiert - Persistenter Key-Value-Speicher diff --git a/docs/src/content/docs/de/development/codegen.md b/docs/src/content/docs/de/development/codegen.md index 40dbdcbc6..88aebe33f 100644 --- a/docs/src/content/docs/de/development/codegen.md +++ b/docs/src/content/docs/de/development/codegen.md @@ -21,8 +21,11 @@ Führen Sie sie aus nach der Änderung von: # Gesamten Code generieren dart run build_runner build --delete-conflicting-outputs -# Bereinigen und neu generieren -dart run build_runner build --delete-conflicting-outputs --clean +# Generierten Build-Cache bereinigen +dart run build_runner clean + +# Dann neu generieren +dart run build_runner build --delete-conflicting-outputs ``` ## Generierte Dateien @@ -94,5 +97,5 @@ 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. +- Behalten Sie generierte Dateien in der Versionsverwaltung, wenn dieses Repository sie bereits verfolgt. - Bearbeiten Sie generierte Dateien niemals manuell. diff --git a/docs/src/content/docs/de/development/testing.md b/docs/src/content/docs/de/development/testing.md index 0172c3ed7..eb7980042 100644 --- a/docs/src/content/docs/de/development/testing.md +++ b/docs/src/content/docs/de/development/testing.md @@ -18,17 +18,7 @@ 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 -``` +Tests befinden sich im Verzeichnis `test/`. Die aktuelle Suite ist überwiegend flach und nach Parser-, Modell- und Utility-Verhalten gruppiert, zum Beispiel `cpu_test.dart`, `container_test.dart` und `ssh_config_test.dart`. ## Unit-Tests @@ -71,26 +61,13 @@ test('serverStatusProvider gibt Status zurück', () async { }); ``` -## Mocking - -Mocks für externe Abhängigkeiten verwenden: - -```dart -class MockSshService extends Mock implements SshService {} +## Externe Abhängigkeiten -test('verbindet zum Server', () async { - final mockSsh = MockSshService(); - when(mockSsh.connect(any)).thenAnswer((_) async => true); - - // Test mit Mock -}); -``` +Vermeiden Sie Tests, die von echten SSH-Servern abhängen. Parser-, Modell- und Command-Builder-Tests sollten deterministisch bleiben; fügen Sie gezielte Fakes oder Fixtures hinzu, wenn eine Funktion eine Service-Grenze einführt. ## Integrationstests -Komplette Benutzerabläufe testen (in `integration_test/`): - -```dart +Im aktuellen Repository gibt es keine `integration_test/`-Suite. Fügen Sie Integrationstests nur hinzu, wenn eine Funktion End-to-End-Geräte- oder App-Flow-Abdeckung benötigt.dart testWidgets('Server hinzufügen Ablauf', (tester) async { await tester.pumpWidget(MyApp()); diff --git a/docs/src/content/docs/de/index.mdx b/docs/src/content/docs/de/index.mdx index 04631f9b5..171a37194 100644 --- a/docs/src/content/docs/de/index.mdx +++ b/docs/src/content/docs/de/index.mdx @@ -5,7 +5,7 @@ hero: tagline: Verwalten Sie Ihre Linux-Server von überall aus actions: - text: Loslegen - link: /de/introduction/ + link: /docs/de/introduction/ icon: right-arrow variant: primary - text: Auf GitHub ansehen @@ -41,6 +41,6 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; ## 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/) +- **Download**: Verfügbar im [App Store](https://apps.apple.com/app/id1586449703), auf [GitHub](https://github.com/lollipopkit/flutter_server_box/releases), bei [F-Droid](https://f-droid.org/packages/tech.lolli.toolbox), im [CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid) und bei [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) - **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 index fa48ef79d..78aacc16f 100644 --- a/docs/src/content/docs/de/installation.mdx +++ b/docs/src/content/docs/de/installation.mdx @@ -15,14 +15,20 @@ Laden Sie es aus dem **[App Store](https://apps.apple.com/app/id1586449703)** he 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 +- **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** – Spiegel für Release-Pakete +- **[F-Droid](https://f-droid.org/packages/tech.lolli.toolbox)** – Für Benutzer, die reine FOSS-Quellen bevorzugen +- **[OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)** – Drittanbieter-Android-App-Store ## Desktop Apps ### macOS -Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Aus dem **[App Store](https://apps.apple.com/app/id1586449703)** herunterladen oder mit Homebrew Cask installieren: + +```sh +brew install --cask server-box +``` Funktionen: - Native Menüleisten-Integration @@ -30,13 +36,15 @@ Funktionen: ### Linux -Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** oder dem **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. -Verfügbar als AppImage, deb oder tar.gz Pakete. +GitHub Releases und CDN stellen Linux-AppImage-Pakete bereit. ### Windows -Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Herunterladen von den **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** oder dem **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. + +GitHub Releases und CDN stellen Windows-zip-Pakete bereit. ## watchOS @@ -44,7 +52,7 @@ Verfügbar im **[App Store](https://apps.apple.com/app/id1586449703)** als Teil ## Aus dem Quellcode bauen -Um Server Box aus dem Quellcode zu bauen, lesen Sie den Abschnitt [Bauen](/de/development/building/) in der Entwicklungsdokumentation. +Um Server Box aus dem Quellcode zu bauen, lesen Sie den Abschnitt [Bauen](/docs/de/development/building/) in der Entwicklungsdokumentation. ## Versionsinformationen diff --git a/docs/src/content/docs/de/principles/architecture.md b/docs/src/content/docs/de/principles/architecture.md index 9c6e59dd9..aba2ef3d2 100644 --- a/docs/src/content/docs/de/principles/architecture.md +++ b/docs/src/content/docs/de/principles/architecture.md @@ -85,7 +85,7 @@ void main() { - Keine Abhängigkeiten von nativem Code - Schneller Key-Value-Speicher - Typsicher durch Codegenerierung -- Keine manuellen Feld-Annotationen erforderlich +- Folgen Sie dem bestehenden Modellmuster; einige verfolgte Modelle verwenden weiterhin explizite Feld-Annotationen **Stores:** - `SettingStore`: App-Einstellungen diff --git a/docs/src/content/docs/development/architecture.md b/docs/src/content/docs/development/architecture.md index 6fb4837a4..639ff7317 100644 --- a/docs/src/content/docs/development/architecture.md +++ b/docs/src/content/docs/development/architecture.md @@ -47,7 +47,7 @@ Server Box follows clean architecture principles with clear separation between d ### Local Storage: Hive - **hive_ce**: Community edition of Hive -- No manual `@HiveField` or `@HiveType` needed +- Follow the existing model pattern: most stores use `hive_ce`, while some tracked models still declare `@HiveType` and `@HiveField` explicitly - Type adapters auto-generated - Persistent key-value storage diff --git a/docs/src/content/docs/development/codegen.md b/docs/src/content/docs/development/codegen.md index 1e714d004..09b65d369 100644 --- a/docs/src/content/docs/development/codegen.md +++ b/docs/src/content/docs/development/codegen.md @@ -21,8 +21,11 @@ Run after modifying: # Generate all code dart run build_runner build --delete-conflicting-outputs -# Clean and regenerate -dart run build_runner build --delete-conflicting-outputs --clean +# Clean generated build cache +dart run build_runner clean + +# Then regenerate +dart run build_runner build --delete-conflicting-outputs ``` ## Generated Files @@ -94,5 +97,5 @@ Generates `lib/generated/l10n/` from `lib/l10n/*.arb` files. ## Tips - Use `--delete-conflicting-outputs` to avoid conflicts -- Add generated files to `.gitignore` +- Keep generated files in version control when they are already tracked by this repository - Never manually edit generated files diff --git a/docs/src/content/docs/development/testing.md b/docs/src/content/docs/development/testing.md index d19b53458..af1c1550e 100644 --- a/docs/src/content/docs/development/testing.md +++ b/docs/src/content/docs/development/testing.md @@ -18,17 +18,7 @@ flutter test --coverage ## Test Structure -Tests are located in the `test/` directory mirroring the lib structure: - -``` -test/ -├── data/ -│ ├── model/ -│ └── provider/ -├── view/ -│ └── widget/ -└── test_helpers.dart -``` +Tests are located in the `test/` directory. The current suite is mostly flat and grouped by parser, model, and utility behavior, for example `cpu_test.dart`, `container_test.dart`, and `ssh_config_test.dart`. ## Unit Tests @@ -71,38 +61,13 @@ test('serverStatusProvider returns status', () async { }); ``` -## Mocking - -Use mocks for external dependencies: +## External Dependencies -```dart -class MockSshService extends Mock implements SshService {} - -test('connects to server', () async { - final mockSsh = MockSshService(); - when(mockSsh.connect(any)).thenAnswer((_) async => true); - - // Test with mock -}); -``` +Avoid tests that depend on real SSH servers. Keep parser, model, and command-builder tests deterministic; add targeted fakes or fixtures when a feature introduces a service boundary. ## Integration Tests -Test complete user flows (in `integration_test/`): - -```dart -testWidgets('add server flow', (tester) async { - await tester.pumpWidget(MyApp()); - - // Tap add button - await tester.tap(find.byIcon(Icons.add)); - await tester.pumpAndSettle(); - - // Fill form - await tester.enterText(find.byKey(Key('name')), 'Test Server'); - // ... -}); -``` +There is no `integration_test/` suite in the current repository. Add integration tests only when a feature needs end-to-end device or app-flow coverage. ## Best Practices diff --git a/docs/src/content/docs/es/advanced/bulk-import.md b/docs/src/content/docs/es/advanced/bulk-import.md index ac5adec2b..03799b8df 100644 --- a/docs/src/content/docs/es/advanced/bulk-import.md +++ b/docs/src/content/docs/es/advanced/bulk-import.md @@ -10,7 +10,7 @@ Importa múltiples configuraciones de servidor a la vez utilizando un archivo JS :::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 +- **Preferir claves SSH** (`pubKeyId`) en lugar de `pwd`; son más seguras - **Usar gestores de secretos** o variables de entorno si debes usar contraseñas - **Eliminar el archivo inmediatamente** después de la importación; no dejes credenciales tiradas - **Añadir a .gitignore**: nunca subas archivos de credenciales al control de versiones @@ -24,7 +24,7 @@ Importa múltiples configuraciones de servidor a la vez utilizando un archivo JS "port": 22, "user": "root", "pwd": "password", - "keyId": "", + "pubKeyId": "", "tags": ["production"], "autoConnect": false } @@ -40,7 +40,7 @@ Importa múltiples configuraciones de servidor a la vez utilizando un archivo JS | `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) | +| `pubKeyId` | No | ID de clave privada (de Claves Privadas - recomendado) | | `tags` | No | Etiquetas de organización | | `autoConnect` | No | Autoconexión al iniciar | @@ -60,7 +60,7 @@ Importa múltiples configuraciones de servidor a la vez utilizando un archivo JS "ip": "prod.example.com", "port": 22, "user": "admin", - "keyId": "mi-clave", + "pubKeyId": "mi-clave", "tags": ["production", "web"] }, { @@ -68,7 +68,7 @@ Importa múltiples configuraciones de servidor a la vez utilizando un archivo JS "ip": "dev.example.com", "port": 2222, "user": "dev", - "keyId": "dev-clave", + "pubKeyId": "dev-clave", "tags": ["development"] } ] diff --git a/docs/src/content/docs/es/development/architecture.md b/docs/src/content/docs/es/development/architecture.md index f74931cac..54a5e470f 100644 --- a/docs/src/content/docs/es/development/architecture.md +++ b/docs/src/content/docs/es/development/architecture.md @@ -47,7 +47,7 @@ Server Box sigue los principios de Clean Architecture con una clara separación ### Almacenamiento Local: Hive - **hive_ce**: Edición comunitaria de Hive -- No se requiere `@HiveField` o `@HiveType` manual +- Sigue el patrón existente: la mayoría de los stores usan `hive_ce`, mientras algunos modelos versionados aún declaran explícitamente `@HiveType` y `@HiveField` - Adaptadores de tipo generados automáticamente - Almacenamiento persistente clave-valor diff --git a/docs/src/content/docs/es/development/codegen.md b/docs/src/content/docs/es/development/codegen.md index d1b13d3ba..b1f09114a 100644 --- a/docs/src/content/docs/es/development/codegen.md +++ b/docs/src/content/docs/es/development/codegen.md @@ -21,8 +21,11 @@ Ejecutar tras modificar: # 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 +# Limpiar la caché de generación +dart run build_runner clean + +# Luego regenerar +dart run build_runner build --delete-conflicting-outputs ``` ## Archivos Generados @@ -94,5 +97,5 @@ 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` +- Mantén los archivos generados en el control de versiones cuando este repositorio ya los sigue - Nunca edites manualmente los archivos generados diff --git a/docs/src/content/docs/es/development/testing.md b/docs/src/content/docs/es/development/testing.md index fa51fd328..33086ba50 100644 --- a/docs/src/content/docs/es/development/testing.md +++ b/docs/src/content/docs/es/development/testing.md @@ -18,17 +18,7 @@ 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 -``` +Las pruebas se encuentran en el directorio `test/`. La suite actual es mayormente plana y se agrupa por comportamiento de parsers, modelos y utilidades, por ejemplo `cpu_test.dart`, `container_test.dart` y `ssh_config_test.dart`. ## Pruebas Unitarias @@ -71,38 +61,13 @@ test('serverStatusProvider devuelve el estado', () async { }); ``` -## Mocking (Simulaciones) - -Utilizar mocks para dependencias externas: +## 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 -}); -``` +Evita pruebas que dependan de servidores SSH reales. Las pruebas de parsers, modelos y constructores de comandos deben ser deterministas; añade fakes o fixtures dirigidos cuando una función introduzca una frontera de servicio. ## Pruebas de Integración -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'); - // ... -}); -``` +El repositorio actual no contiene una suite `integration_test/`. Añade pruebas de integración solo cuando una función necesite cobertura end-to-end de dispositivo o flujo completo de la app. ## Buenas Prácticas diff --git a/docs/src/content/docs/es/index.mdx b/docs/src/content/docs/es/index.mdx index d602382a5..c1e1b8a6a 100644 --- a/docs/src/content/docs/es/index.mdx +++ b/docs/src/content/docs/es/index.mdx @@ -5,7 +5,7 @@ hero: tagline: Administra tus servidores Linux desde cualquier lugar actions: - text: Empezar - link: /es/introduction/ + link: /docs/es/introduction/ icon: right-arrow variant: primary - text: Ver en GitHub @@ -41,6 +41,6 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; ## 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/) +- **Descarga**: Disponible en [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) y [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) - **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 index 0c740d4a7..d3f03439d 100644 --- a/docs/src/content/docs/es/installation.mdx +++ b/docs/src/content/docs/es/installation.mdx @@ -15,14 +15,20 @@ Descárgalo desde la **[App Store](https://apps.apple.com/app/id1586449703)**. 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 +- **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** - Espejo para paquetes de lanzamiento +- **[F-Droid](https://f-droid.org/packages/tech.lolli.toolbox)** - Para usuarios que prefieren fuentes exclusivamente FOSS (Software Libre y de Código Abierto) +- **[OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)** - Tienda Android de terceros ## Aplicaciones de Escritorio ### macOS -Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Descárgalo desde la **[App Store](https://apps.apple.com/app/id1586449703)** o instálalo con Homebrew Cask: + +```sh +brew install --cask server-box +``` Características: - Integración nativa con la barra de menú @@ -30,13 +36,15 @@ Características: ### Linux -Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** o el **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. -Disponible en paquetes AppImage, deb o tar.gz. +GitHub Releases y el CDN proporcionan paquetes Linux AppImage. ### Windows -Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Descárgalo desde **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** o el **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. + +GitHub Releases y el CDN proporcionan paquetes Windows zip. ## watchOS @@ -44,7 +52,7 @@ Disponible en la **[App Store](https://apps.apple.com/app/id1586449703)** como p ## 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. +Para compilar Server Box desde el código fuente, consulta la sección de [Compilación](/docs/es/development/building/) en la documentación de desarrollo. ## Información de Versión diff --git a/docs/src/content/docs/fr/advanced/bulk-import.md b/docs/src/content/docs/fr/advanced/bulk-import.md index 012ff1e3d..f3d319902 100644 --- a/docs/src/content/docs/fr/advanced/bulk-import.md +++ b/docs/src/content/docs/fr/advanced/bulk-import.md @@ -10,7 +10,7 @@ Importez plusieurs configurations de serveur en une seule fois à l'aide d'un fi :::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 +- **Préférer les clés SSH** (`pubKeyId`) au lieu de `pwd` - elles sont plus sûres - **Utiliser des gestionnaires de mots de passe** ou des variables d'environnement si vous devez utiliser des mots de passe - **Supprimer le fichier immédiatement** après l'importation - ne laissez pas traîner des identifiants - **Ajouter au .gitignore** - ne validez jamais de fichiers d'identifiants dans le contrôle de version @@ -24,7 +24,7 @@ Importez plusieurs configurations de serveur en une seule fois à l'aide d'un fi "port": 22, "user": "root", "pwd": "password", - "keyId": "", + "pubKeyId": "", "tags": ["production"], "autoConnect": false } @@ -40,9 +40,10 @@ Importez plusieurs configurations de serveur en une seule fois à l'aide d'un fi | `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é) | +| `pubKeyId` | Non | ID de clé privée (à partir des clés privées - recommandé) | | `tags` | Non | Tags d'organisation | | `autoConnect` | Non | Connexion automatique au démarrage | +| `id` | Non | ID serveur stable ; les valeurs absentes ou vides sont générées à l'import | ## Étapes d'importation @@ -60,7 +61,7 @@ Importez plusieurs configurations de serveur en une seule fois à l'aide d'un fi "ip": "prod.example.com", "port": 22, "user": "admin", - "keyId": "my-key", + "pubKeyId": "my-key", "tags": ["production", "web"] }, { @@ -68,7 +69,7 @@ Importez plusieurs configurations de serveur en une seule fois à l'aide d'un fi "ip": "dev.example.com", "port": 2222, "user": "dev", - "keyId": "dev-key", + "pubKeyId": "dev-key", "tags": ["development"] } ] diff --git a/docs/src/content/docs/fr/development/architecture.md b/docs/src/content/docs/fr/development/architecture.md index 7e2fa2ab0..78a2595ca 100644 --- a/docs/src/content/docs/fr/development/architecture.md +++ b/docs/src/content/docs/fr/development/architecture.md @@ -47,7 +47,7 @@ Server Box suit les principes de la Clean Architecture avec une séparation clai ### Stockage local : Hive - **hive_ce** : Édition communautaire de Hive -- Pas de `@HiveField` ou `@HiveType` manuel requis +- Suivez le modèle existant : la plupart des stores utilisent `hive_ce`, tandis que certains modèles suivis déclarent encore explicitement `@HiveType` et `@HiveField` - Adaptateurs de type auto-générés - Stockage clé-valeur persistant diff --git a/docs/src/content/docs/fr/development/codegen.md b/docs/src/content/docs/fr/development/codegen.md index d2e81c00c..3b8be82a2 100644 --- a/docs/src/content/docs/fr/development/codegen.md +++ b/docs/src/content/docs/fr/development/codegen.md @@ -21,8 +21,11 @@ Server Box utilise intensivement la génération de code pour les modèles, la g # 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 +# Nettoyer le cache de génération +dart run build_runner clean + +# Puis régénérer +dart run build_runner build --delete-conflicting-outputs ``` ## Fichiers générés @@ -94,5 +97,5 @@ 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` +- Conservez les fichiers générés dans le contrôle de version lorsqu'ils sont déjà suivis par ce dépôt - Ne modifiez jamais manuellement les fichiers générés diff --git a/docs/src/content/docs/fr/development/testing.md b/docs/src/content/docs/fr/development/testing.md index dac0d4ef8..0441bf401 100644 --- a/docs/src/content/docs/fr/development/testing.md +++ b/docs/src/content/docs/fr/development/testing.md @@ -18,17 +18,7 @@ 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 -``` +Les tests se trouvent dans le répertoire `test/`. La suite actuelle est principalement plate et regroupée par comportement de parseur, de modèle et d’utilitaire, par exemple `cpu_test.dart`, `container_test.dart` et `ssh_config_test.dart`. ## Tests unitaires @@ -71,26 +61,13 @@ test('serverStatusProvider retourne le statut', () async { }); ``` -## Mocking (Simulations) - -Utiliser des mocks pour les dépendances externes : - -```dart -class MockSshService extends Mock implements SshService {} +## Dépendances externes -test('se connecte au serveur', () async { - final mockSsh = MockSshService(); - when(mockSsh.connect(any)).thenAnswer((_) async => true); - - // Tester avec le mock -}); -``` +Évitez les tests qui dépendent de vrais serveurs SSH. Les tests de parseurs, modèles et constructeurs de commandes doivent rester déterministes ; ajoutez des fakes ou fixtures ciblés lorsqu’une fonctionnalité introduit une frontière de service. ## Tests d'intégration -Tester des flux utilisateurs complets (dans `integration_test/`) : - -```dart +Le dépôt actuel ne contient pas de suite `integration_test/`. Ajoutez des tests d’intégration seulement lorsqu’une fonctionnalité nécessite une couverture end-to-end sur appareil ou flux applicatif complet.dart testWidgets('flux d\'ajout de serveur', (tester) async { await tester.pumpWidget(MyApp()); diff --git a/docs/src/content/docs/fr/index.mdx b/docs/src/content/docs/fr/index.mdx index a5fdf0cd1..8b7ccc116 100644 --- a/docs/src/content/docs/fr/index.mdx +++ b/docs/src/content/docs/fr/index.mdx @@ -5,7 +5,7 @@ hero: tagline: Gérez vos serveurs Linux de n'importe où actions: - text: Commencer - link: /fr/introduction/ + link: /docs/fr/introduction/ icon: right-arrow variant: primary - text: Voir sur GitHub @@ -41,6 +41,6 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; ## 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/) +- **Téléchargement**: Disponible sur l'[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) et [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) - **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 index 7f2db0538..af47d1ca8 100644 --- a/docs/src/content/docs/fr/installation.mdx +++ b/docs/src/content/docs/fr/installation.mdx @@ -15,14 +15,20 @@ Téléchargez depuis l'**[App Store](https://apps.apple.com/app/id1586449703)**. 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 +- **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** - Miroir pour les paquets de publication +- **[F-Droid](https://f-droid.org/packages/tech.lolli.toolbox)** - Pour les utilisateurs qui préfèrent les sources exclusivement FOSS +- **[OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)** - Boutique Android tierce ## Applications de Bureau ### macOS -Téléchargez depuis les **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Téléchargez depuis l’**[App Store](https://apps.apple.com/app/id1586449703)** ou installez avec Homebrew Cask : + +```sh +brew install --cask server-box +``` Caractéristiques : - Intégration native de la barre de menus @@ -30,13 +36,15 @@ Caractéristiques : ### Linux -Téléchargez depuis les **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Téléchargez depuis les **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** ou le **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. -Disponible en packages AppImage, deb ou tar.gz. +GitHub Releases et le CDN fournissent des paquets Linux AppImage. ### Windows -Téléchargez depuis les **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Téléchargez depuis les **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** ou le **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. + +GitHub Releases et le CDN fournissent des paquets Windows zip. ## watchOS @@ -44,7 +52,7 @@ Disponible sur l'**[App Store](https://apps.apple.com/app/id1586449703)** en tan ## Construction à partir des sources -Pour construire Server Box à partir des sources, consultez la section [Construction](/fr/development/building/) dans la documentation de développement. +Pour construire Server Box à partir des sources, consultez la section [Construction](/docs/fr/development/building/) dans la documentation de développement. ## Informations sur la version diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index dd90afa4a..ce3a8b67c 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -5,7 +5,7 @@ hero: tagline: Manage your Linux servers from anywhere actions: - text: Get Started - link: /introduction/ + link: /docs/introduction/ icon: right-arrow variant: primary - text: View on GitHub @@ -41,6 +41,6 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; ## Quick Links -- **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/) +- **Download**: Available on [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), and [OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/) - **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 22c073e3d..45c7244b3 100644 --- a/docs/src/content/docs/installation.mdx +++ b/docs/src/content/docs/installation.mdx @@ -15,14 +15,20 @@ Download from the **[App Store](https://apps.apple.com/app/id1586449703)**. Choose your preferred source: -- **[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 +- **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** - Mirror for release packages +- **[F-Droid](https://f-droid.org/packages/tech.lolli.toolbox)** - For users who prefer FOSS-only sources +- **[OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)** - Third-party Android app store ## Desktop Apps ### macOS -Download from **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Download from the **[App Store](https://apps.apple.com/app/id1586449703)** or install with Homebrew Cask: + +```sh +brew install --cask server-box +``` Features: - Native menu bar integration @@ -30,13 +36,15 @@ Features: ### Linux -Download from **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Download from **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** or the **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. -Available as AppImage, deb, or tar.gz packages. +GitHub Releases and the CDN provide Linux AppImage packages. ### Windows -Download from **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)**. +Download from **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** or the **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)**. + +GitHub Releases and the CDN provide Windows zip packages. ## watchOS @@ -44,7 +52,7 @@ Available on the **[App Store](https://apps.apple.com/app/id1586449703)** as par ## Building from Source -To build Server Box from source, see the [Building](/development/building) section in the Development documentation. +To build Server Box from source, see the [Building](/docs/development/building/) section in the Development documentation. ## Version Information diff --git a/docs/src/content/docs/ja/advanced/bulk-import.md b/docs/src/content/docs/ja/advanced/bulk-import.md index 4a49c268b..d5a32a5a8 100644 --- a/docs/src/content/docs/ja/advanced/bulk-import.md +++ b/docs/src/content/docs/ja/advanced/bulk-import.md @@ -10,7 +10,7 @@ JSON ファイルを使用して、複数のサーバー設定を一度にイン :::danger[セキュリティ警告] **プレーンテキストのパスワードをファイルに保存しないでください!** この JSON の例ではデモンストレーションのためにパスワードフィールドを表示していますが、以下の点に注意してください。 -- **SSH キーを優先** (`keyId`) し、`pwd` の使用は避けてください。その方が安全です。 +- **SSH キーを優先** (`pubKeyId`) し、`pwd` の使用は避けてください。その方が安全です。 - パスワードを使用する必要がある場合は、**シークレットマネージャー**や環境変数を使用してください。 - インポート後は**直ちにファイルを削除**してください。資格情報を放置しないでください。 - **.gitignore に追加**してください。資格情報ファイルをバージョン管理にコミットしないでください。 @@ -24,7 +24,7 @@ JSON ファイルを使用して、複数のサーバー設定を一度にイン "port": 22, "user": "root", "pwd": "password", - "keyId": "", + "pubKeyId": "", "tags": ["production"], "autoConnect": false } @@ -40,9 +40,10 @@ JSON ファイルを使用して、複数のサーバー設定を一度にイン | `port` | はい | SSH ポート (通常は 22) | | `user` | はい | SSH ユーザー名 | | `pwd` | いいえ | パスワード (非推奨 - 代わりに SSH キーを使用してください) | -| `keyId` | いいえ | SSH キー名 (「非公開鍵」から取得 - 推奨) | +| `pubKeyId` | いいえ | 秘密鍵レコードの ID(「非公開鍵」から取得 - 推奨) | | `tags` | いいえ | 整理用タグ | | `autoConnect` | いいえ | 起動時に自動接続 | +| `id` | いいえ | レコードの一意識別子(省略または空の場合はインポート時に自動生成) | ## インポートの手順 @@ -60,7 +61,7 @@ JSON ファイルを使用して、複数のサーバー設定を一度にイン "ip": "prod.example.com", "port": 22, "user": "admin", - "keyId": "my-key", + "pubKeyId": "my-key", "tags": ["production", "web"] }, { @@ -68,7 +69,7 @@ JSON ファイルを使用して、複数のサーバー設定を一度にイン "ip": "dev.example.com", "port": 2222, "user": "dev", - "keyId": "dev-key", + "pubKeyId": "dev-key", "tags": ["development"] } ] diff --git a/docs/src/content/docs/ja/development/architecture.md b/docs/src/content/docs/ja/development/architecture.md index 884708726..47c66f405 100644 --- a/docs/src/content/docs/ja/development/architecture.md +++ b/docs/src/content/docs/ja/development/architecture.md @@ -47,7 +47,7 @@ Server Box は、データ層、ドメイン層、プレゼンテーション層 ### ローカルストレージ: Hive - **hive_ce**: Hive のコミュニティ版を使用 -- 手動での `@HiveField` や `@HiveType` の指定が不要 +- 既存のモデルパターンに従ってください。多くの store は `hive_ce` を使いますが、一部の追跡済みモデルは引き続き `@HiveType` と `@HiveField` を明示します - 型アダプターの自動生成 - 永続的なキーバリューストレージ diff --git a/docs/src/content/docs/ja/development/codegen.md b/docs/src/content/docs/ja/development/codegen.md index 5dc6d1267..e4ccae905 100644 --- a/docs/src/content/docs/ja/development/codegen.md +++ b/docs/src/content/docs/ja/development/codegen.md @@ -21,8 +21,11 @@ Server Box では、モデル、状態管理、シリアライズのためにコ # すべてのコードを生成 dart run build_runner build --delete-conflicting-outputs -# クリーンアップして再生成 -dart run build_runner build --delete-conflicting-outputs --clean +# 生成キャッシュをクリーン +dart run build_runner clean + +# その後再生成 +dart run build_runner build --delete-conflicting-outputs ``` ## 生成されるファイル @@ -94,5 +97,5 @@ flutter gen-l10n ## ヒント - 競合を避けるために `--delete-conflicting-outputs` を使用してください。 -- 生成されたファイルを `.gitignore` に追加してください。 +- このリポジトリで既に追跡されている生成ファイルは、引き続きバージョン管理に含めてください。 - 生成されたファイルを手動で編集しないでください。 diff --git a/docs/src/content/docs/ja/development/testing.md b/docs/src/content/docs/ja/development/testing.md index 481e708a5..fde6e0812 100644 --- a/docs/src/content/docs/ja/development/testing.md +++ b/docs/src/content/docs/ja/development/testing.md @@ -18,17 +18,7 @@ flutter test --coverage ## テスト構造 -テストは `test/` ディレクトリにあり、lib の構造を反映しています: - -``` -test/ -├── data/ -│ ├── model/ -│ └── provider/ -├── view/ -│ └── widget/ -└── test_helpers.dart -``` +テストは `test/` ディレクトリにあります。現在のテストスイートは主にフラットな構成で、パーサー、モデル、ユーティリティの挙動ごとに分かれています。例: `cpu_test.dart`、`container_test.dart`、`ssh_config_test.dart`。 ## ユニットテスト @@ -71,26 +61,13 @@ test('serverStatusProvider がステータスを返すこと', () async { }); ``` -## モック (Mocking) - -外部依存関係にモックを使用する: - -```dart -class MockSshService extends Mock implements SshService {} +## 外部依存 -test('サーバーに接続すること', () async { - final mockSsh = MockSshService(); - when(mockSsh.connect(any)).thenAnswer((_) async => true); - - // モックを使用してテスト -}); -``` +実際の SSH サーバーに依存するテストは避けてください。パーサー、モデル、コマンドビルダーのテストは決定的に保ち、機能がサービス境界を導入する場合にのみ対象を絞った fake や fixture を追加してください。 ## 統合テスト -完全なユーザーフローのテスト (`integration_test/` 内): - -```dart +現在のリポジトリには `integration_test/` スイートはありません。デバイス上の end-to-end やアプリ全体のフロー確認が必要な機能でのみ追加してください。dart testWidgets('サーバー追加フロー', (tester) async { await tester.pumpWidget(MyApp()); diff --git a/docs/src/content/docs/ja/index.mdx b/docs/src/content/docs/ja/index.mdx index dd43e86b3..33ebdfdf7 100644 --- a/docs/src/content/docs/ja/index.mdx +++ b/docs/src/content/docs/ja/index.mdx @@ -5,7 +5,7 @@ hero: tagline: どこからでも Linux サーバーを管理 actions: - text: はじめる - link: /ja/introduction/ + link: /docs/ja/introduction/ icon: right-arrow variant: primary - text: GitHub で見る @@ -41,6 +41,6 @@ import { Card, CardGrid } from '@astrojs/starlight/components'; ## クイックリンク -- **ダウンロード**: [App Store](https://apps.apple.com/app/id1586449703)、[GitHub](https://github.com/lollipopkit/flutter_server_box/releases)、[F-Droid](https://f-droid.org/) で入手可能 +- **ダウンロード**: [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/ja/installation.mdx b/docs/src/content/docs/ja/installation.mdx index 732a28aa9..3acdbacc8 100644 --- a/docs/src/content/docs/ja/installation.mdx +++ b/docs/src/content/docs/ja/installation.mdx @@ -15,14 +15,20 @@ Server Box は複数のプラットフォームで利用可能です。お好み お好みのソースを選択してください: -- **[F-Droid](https://f-droid.org/)** - FOSS(自由でオープンソースのソフトウェア)のみのソースを好むユーザー向け - **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** - ソースから直接最新バージョンを入手したいユーザー向け +- **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** - リリースパッケージのミラー +- **[F-Droid](https://f-droid.org/packages/tech.lolli.toolbox)** - FOSS(自由でオープンソースのソフトウェア)のみのソースを好むユーザー向け +- **[OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)** - サードパーティの Android アプリストア ## デスクトップアプリ ### macOS -**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** からダウンロードしてください。 +**[App Store](https://apps.apple.com/app/id1586449703)** からダウンロードするか、Homebrew Cask でインストールしてください: + +```sh +brew install --cask server-box +``` 特徴: - ネイティブメニューバーへの統合 @@ -30,13 +36,15 @@ Server Box は複数のプラットフォームで利用可能です。お好み ### Linux -**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** からダウンロードしてください。 +**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** または **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** からダウンロードしてください。 -AppImage、deb、または tar.gz パッケージとして利用可能です。 +GitHub Releases と CDN で Linux AppImage パッケージを提供しています。 ### Windows -**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** からダウンロードしてください。 +**[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** または **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** からダウンロードしてください。 + +GitHub Releases と CDN で Windows zip パッケージを提供しています。 ## watchOS @@ -44,7 +52,7 @@ iOS アプリの一部として **[App Store](https://apps.apple.com/app/id15864 ## ソースからのビルド -Server Box をソースからビルドするには、開発ドキュメントの [ビルド](/ja/development/building/) セクションを参照してください。 +Server Box をソースからビルドするには、開発ドキュメントの [ビルド](/docs/ja/development/building/) セクションを参照してください。 ## バージョン情報 diff --git a/docs/src/content/docs/ja/principles/architecture.md b/docs/src/content/docs/ja/principles/architecture.md index 8f2d82bcf..e41851e6d 100644 --- a/docs/src/content/docs/ja/principles/architecture.md +++ b/docs/src/content/docs/ja/principles/architecture.md @@ -85,7 +85,7 @@ void main() { - ネイティブコードへの依存がない - 高速なキーバリューストレージ - コード生成による型安全性 -- 手動でのフィールドアノテーションが不要 +- 既存のモデルパターンに従ってください。一部の追跡済みモデルは引き続き明示的なフィールドアノテーションを使用します **ストア:** - `SettingStore`: アプリの設定 diff --git a/docs/src/content/docs/principles/architecture.md b/docs/src/content/docs/principles/architecture.md index 39887795b..50902d0be 100644 --- a/docs/src/content/docs/principles/architecture.md +++ b/docs/src/content/docs/principles/architecture.md @@ -85,7 +85,7 @@ void main() { - No native code dependencies - Fast key-value storage - Type-safe with code generation -- No manual field annotations needed +- Follow the existing model pattern; some tracked models still use explicit field annotations **Stores:** - `SettingStore`: App preferences diff --git a/docs/src/content/docs/zh/advanced/bulk-import.md b/docs/src/content/docs/zh/advanced/bulk-import.md index 056726ce3..8a92b34ef 100644 --- a/docs/src/content/docs/zh/advanced/bulk-import.md +++ b/docs/src/content/docs/zh/advanced/bulk-import.md @@ -10,7 +10,7 @@ description: 从 JSON 文件中导入多个服务器 :::danger[安全警告] **切勿在文件中存储明文密码!** 此 JSON 示例仅为了演示显示了密码字段,但你应该: -- **优先使用 SSH 密钥** (`keyId`) 而不是 `pwd` - 它们更安全 +- **优先使用 SSH 密钥** (`pubKeyId`) 而不是 `pwd` - 它们更安全 - 如果必须使用密码,请使用**密码管理器**或环境变量 - 导入后**立即删除文件** - 不要让凭据散落在各处 - **添加到 .gitignore** - 切勿将凭证文件提交到版本控制 @@ -24,7 +24,7 @@ description: 从 JSON 文件中导入多个服务器 "port": 22, "user": "root", "pwd": "password", - "keyId": "", + "pubKeyId": "", "tags": ["production"], "autoConnect": false } @@ -40,9 +40,10 @@ description: 从 JSON 文件中导入多个服务器 | `port` | 是 | SSH 端口 (通常为 22) | | `user` | 是 | SSH 用户名 | | `pwd` | 否 | 密码 (不建议使用 - 请改用 SSH 密钥) | -| `keyId` | 否 | SSH 密钥名称 (来自“私钥” - 推荐) | +| `pubKeyId` | 否 | 私钥记录 id(来自“私钥” - 推荐) | | `tags` | 否 | 组织标签 | | `autoConnect` | 否 | 启动时自动连接 | +| `id` | 否 | 稳定的服务器 id;省略或为空时会在导入时生成 | ## 导入步骤 @@ -60,7 +61,7 @@ description: 从 JSON 文件中导入多个服务器 "ip": "prod.example.com", "port": 22, "user": "admin", - "keyId": "my-key", + "pubKeyId": "my-key", "tags": ["production", "web"] }, { @@ -68,7 +69,7 @@ description: 从 JSON 文件中导入多个服务器 "ip": "dev.example.com", "port": 2222, "user": "dev", - "keyId": "dev-key", + "pubKeyId": "dev-key", "tags": ["development"] } ] diff --git a/docs/src/content/docs/zh/development/architecture.md b/docs/src/content/docs/zh/development/architecture.md index 74cc8308f..77322caea 100644 --- a/docs/src/content/docs/zh/development/architecture.md +++ b/docs/src/content/docs/zh/development/architecture.md @@ -47,7 +47,7 @@ Server Box 遵循整洁架构 (Clean Architecture) 原则,在数据层、领 ### 本地存储:Hive - **hive_ce**:Hive 的社区版 -- 无需手动添加 `@HiveField` 或 `@HiveType` 注解 +- 遵循现有模型模式:多数存储使用 `hive_ce`,部分已跟踪模型仍显式声明 `@HiveType` 和 `@HiveField` - 类型适配器 (Type adapters) 自动生成 - 持久化键值对存储 diff --git a/docs/src/content/docs/zh/development/codegen.md b/docs/src/content/docs/zh/development/codegen.md index c6c6d1b61..6d09ebfaa 100644 --- a/docs/src/content/docs/zh/development/codegen.md +++ b/docs/src/content/docs/zh/development/codegen.md @@ -17,12 +17,21 @@ Server Box 大量使用代码生成技术来处理模型、状态管理和序列 ## 运行代码生成 +### 普通构建 + +用于常规代码生成: + ```bash -# 生成所有代码 dart run build_runner build --delete-conflicting-outputs +``` + +### 清理后重建 -# 清理并重新生成 -dart run build_runner build --delete-conflicting-outputs --clean +仅在生成缓存异常或生成结果不一致时使用。先清理,再重新生成: + +```bash +dart run build_runner clean +dart run build_runner build --delete-conflicting-outputs ``` ## 生成的文件类型 @@ -94,5 +103,5 @@ flutter gen-l10n ## 提示 - 使用 `--delete-conflicting-outputs` 避免冲突 -- 将生成的文件添加到 `.gitignore` +- 如果生成文件已被本仓库跟踪,请继续提交这些生成文件 - **切勿**手动编辑生成的文件 diff --git a/docs/src/content/docs/zh/development/testing.md b/docs/src/content/docs/zh/development/testing.md index 6ef7a0ed5..954af156c 100644 --- a/docs/src/content/docs/zh/development/testing.md +++ b/docs/src/content/docs/zh/development/testing.md @@ -18,17 +18,7 @@ flutter test --coverage ## 测试结构 -测试文件位于 `test/` 目录中,其结构与 `lib` 目录保持一致: - -``` -test/ -├── data/ -│ ├── model/ -│ └── provider/ -├── view/ -│ └── widget/ -└── test_helpers.dart -``` +测试位于 `test/` 目录中。当前测试套件基本是扁平结构,按解析器、模型和工具行为分组,例如 `cpu_test.dart`、`container_test.dart` 和 `ssh_config_test.dart`。 ## 单元测试 @@ -71,26 +61,13 @@ test('serverStatusProvider 应当返回状态', () async { }); ``` -## Mock 模拟 - -对外部依赖使用 Mock 模拟: - -```dart -class MockSshService extends Mock implements SshService {} +## 外部依赖 -test('应当能连接到服务器', () async { - final mockSsh = MockSshService(); - when(mockSsh.connect(any)).thenAnswer((_) async => true); - - // 使用 mock 进行测试 -}); -``` +避免让测试依赖真实 SSH 服务器。解析器、模型和命令构建测试应保持确定性;当功能引入服务边界时,再添加有针对性的 fake 或 fixture。 ## 集成测试 -测试完整的用户流程(位于 `integration_test/`): - -```dart +当前仓库没有 `integration_test/` 测试套件。只有当功能需要端到端设备或完整应用流程覆盖时,再新增集成测试。dart testWidgets('添加服务器流程', (tester) async { await tester.pumpWidget(MyApp()); diff --git a/docs/src/content/docs/zh/index.mdx b/docs/src/content/docs/zh/index.mdx index 3a12af7ef..becea8e65 100644 --- a/docs/src/content/docs/zh/index.mdx +++ b/docs/src/content/docs/zh/index.mdx @@ -5,7 +5,7 @@ hero: tagline: 随时随地管理您的 Linux 服务器 actions: - text: 开始使用 - link: /zh/introduction/ + link: /docs/zh/introduction/ icon: right-arrow variant: primary - text: 在 GitHub 上查看 diff --git a/docs/src/content/docs/zh/installation.mdx b/docs/src/content/docs/zh/installation.mdx index bf2f6192d..ad6dfbe13 100644 --- a/docs/src/content/docs/zh/installation.mdx +++ b/docs/src/content/docs/zh/installation.mdx @@ -15,16 +15,20 @@ Server Box 适用于多个平台。选择您偏好的安装方式。 选择您偏好的来源: -- **[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 +- **[F-Droid](https://f-droid.org/packages/tech.lolli.toolbox)** - 适合偏好 FOSS 来源的用户 - **[OpenAPK](https://www.openapk.net/serverbox/tech.lolli.toolbox/)** - 第三方应用商店 ## 桌面应用 ### macOS -从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 下载。 +从 **[App Store](https://apps.apple.com/app/id1586449703)** 下载,或使用 Homebrew Cask 安装: + +```sh +brew install --cask server-box +``` 特性: - 原生菜单栏集成 @@ -32,13 +36,15 @@ Server Box 适用于多个平台。选择您偏好的安装方式。 ### Linux -从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 下载。 +从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 或 **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** 下载。 -提供 AppImage、deb 或 tar.gz 格式包。 +GitHub Releases 和 CDN 提供 Linux AppImage 包。 ### Windows -从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 下载。 +从 **[GitHub Releases](https://github.com/lollipopkit/flutter_server_box/releases)** 或 **[CDN](https://cdn.lpkt.cn/serverbox/pkg/?sort=time&order=desc&layout=grid)** 下载。 + +GitHub Releases 和 CDN 提供 Windows zip 包。 ## watchOS @@ -46,7 +52,7 @@ Server Box 适用于多个平台。选择您偏好的安装方式。 ## 从源码构建 -要从源码构建 Server Box,请参阅开发文档中的[构建](/zh/development/building)部分。 +要从源码构建 Server Box,请参阅开发文档中的[构建](/docs/zh/development/building/)部分。 ## 版本信息 diff --git a/docs/src/content/docs/zh/principles/architecture.md b/docs/src/content/docs/zh/principles/architecture.md index ce792e46e..4a2d743fe 100644 --- a/docs/src/content/docs/zh/principles/architecture.md +++ b/docs/src/content/docs/zh/principles/architecture.md @@ -85,7 +85,7 @@ void main() { - 无原生代码依赖 - 快速的键值存储 - 通过代码生成实现类型安全 -- 无需手动添加字段注解 +- 遵循现有模型模式;部分已跟踪模型仍显式使用字段注解 **存储类:** - `SettingStore`:应用偏好设置 diff --git a/scripts/build-cloudflare-pages.sh b/scripts/build-cloudflare-pages.sh new file mode 100644 index 000000000..12a17f95d --- /dev/null +++ b/scripts/build-cloudflare-pages.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +cd "$ROOT_DIR/website" +bun install --frozen-lockfile +bun run build + +cd "$ROOT_DIR/docs" +bun install +bun run build + +rm -rf "$ROOT_DIR/website/dist/docs" +mkdir -p "$ROOT_DIR/website/dist/docs" +cp -R "$ROOT_DIR/docs/dist/." "$ROOT_DIR/website/dist/docs/" diff --git a/scripts/release/package-dmg-from-xcarchive.sh b/scripts/release/package-dmg-from-xcarchive.sh index f68b0d3a7..c07972a5b 100755 --- a/scripts/release/package-dmg-from-xcarchive.sh +++ b/scripts/release/package-dmg-from-xcarchive.sh @@ -41,7 +41,7 @@ fi APP_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$INFO_PLIST")" APP_BUILD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$INFO_PLIST")" -DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${APP_VERSION}-${APP_BUILD}}" +DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${APP_VERSION}}" DMG_PATH="${DMG_PATH:-$ARTIFACTS_PATH/${DMG_BASENAME}.dmg}" mkdir -p "$ARTIFACTS_PATH" diff --git a/scripts/release/release-macos-dmg.sh b/scripts/release/release-macos-dmg.sh index 20fae1cd1..999c5c25d 100755 --- a/scripts/release/release-macos-dmg.sh +++ b/scripts/release/release-macos-dmg.sh @@ -47,6 +47,39 @@ require_cmd() { fi } +warn_pre_notary_spctl_rejection() { + local app_path="$1" + + echo "Skipping pre-notarization spctl enforcement for $app_path" + echo 'Developer ID signed apps can be rejected by Gatekeeper before notarization with source=Unnotarized Developer ID' +} + +ensure_macos_pods_synced() { + local macos_dir="$REPO_ROOT/macos" + local podfile_lock="$macos_dir/Podfile.lock" + local manifest_lock="$macos_dir/Pods/Manifest.lock" + local pods_project="$macos_dir/Pods/Pods.xcodeproj" + + require_cmd pod + + if [[ ! -f "$manifest_lock" || ! -d "$pods_project" ]]; then + echo 'CocoaPods sandbox is incomplete, running pod install' + ( + cd "$macos_dir" + pod install + ) + return + fi + + if [[ -f "$podfile_lock" ]] && ! cmp -s "$podfile_lock" "$manifest_lock"; then + echo 'CocoaPods sandbox is out of sync, running pod install' + ( + cd "$macos_dir" + pod install + ) + fi +} + normalize_abs_path() { local path="$1" if [[ -z "$path" ]]; then @@ -232,13 +265,14 @@ if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then fi require_file "$WORKSPACE_PATH" +ensure_macos_pods_synced read -r DEFAULT_MARKETING_VERSION DEFAULT_CURRENT_PROJECT_VERSION <<<"$(read_pubspec_versions)" MARKETING_VERSION="${MARKETING_VERSION_OVERRIDE:-$DEFAULT_MARKETING_VERSION}" CURRENT_PROJECT_VERSION="${CURRENT_PROJECT_VERSION_OVERRIDE:-$DEFAULT_CURRENT_PROJECT_VERSION}" RELEASE_TAG="${RELEASE_TAG:-v${MARKETING_VERSION}}" RELEASE_TITLE="${RELEASE_TITLE:-$RELEASE_TAG}" -DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${MARKETING_VERSION}-${CURRENT_PROJECT_VERSION}}" +DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${MARKETING_VERSION}}" DMG_PATH="${DMG_PATH:-$ARTIFACTS_PATH/${DMG_BASENAME}.dmg}" validate_allowed_path 'BUILD_ROOT' "$BUILD_ROOT" >/dev/null @@ -303,7 +337,7 @@ if [[ ! -d "$APP_PATH" ]]; then fi codesign --verify --deep --strict --verbose=2 "$APP_PATH" -spctl -a -t exec -vv "$APP_PATH" +warn_pre_notary_spctl_rejection "$APP_PATH" APP_PATH="$APP_PATH" \ APP_NAME="$APP_NAME" \ @@ -313,7 +347,7 @@ ARTIFACTS_PATH="$ARTIFACTS_PATH" \ DMG_STAGING_PATH="$DMG_STAGING_PATH" \ DMG_BASENAME="$DMG_BASENAME" \ DMG_PATH="$DMG_PATH" \ -REQUIRE_SPCTL=1 \ +REQUIRE_SPCTL=0 \ bash "$SCRIPT_DIR/package-dmg-from-xcarchive.sh" codesign --force --sign "$SIGNING_IDENTITY" --timestamp "$DMG_PATH" @@ -325,6 +359,7 @@ xcrun notarytool submit "$DMG_PATH" \ xcrun stapler staple "$DMG_PATH" xcrun stapler validate "$DMG_PATH" +spctl -a -t open --context context:primary-signature -vv "$DMG_PATH" if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then if gh release view "$RELEASE_TAG" --repo "$APP_REPO_SLUG" >/dev/null 2>&1; then @@ -343,6 +378,13 @@ if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then --clobber fi +if [[ "${SYNC_HOMEBREW_CASK:-1}" == "1" ]]; then + APP_PATH="$APP_PATH" \ + DMG_PATH="$DMG_PATH" \ + TAP_REPO_PATH="${TAP_REPO_PATH:-$HOME/proj/homebrew-taps}" \ + bash "$SCRIPT_DIR/sync-homebrew-cask.sh" +fi + echo "Release complete" echo "Marketing version: $MARKETING_VERSION" echo "Build number: $CURRENT_PROJECT_VERSION" @@ -352,3 +394,6 @@ echo "DMG: $DMG_PATH" if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then echo "GitHub release: $APP_REPO_SLUG $RELEASE_TAG" fi +if [[ "${SYNC_HOMEBREW_CASK:-1}" == "1" ]]; then + echo "Homebrew cask: ${TAP_REPO_PATH:-$HOME/proj/homebrew-taps}/Casks/server-box.rb" +fi diff --git a/scripts/release/sync-homebrew-cask.sh b/scripts/release/sync-homebrew-cask.sh new file mode 100755 index 000000000..71470d853 --- /dev/null +++ b/scripts/release/sync-homebrew-cask.sh @@ -0,0 +1,112 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +APP_NAME="${APP_NAME:-Server Box}" +CASK_NAME="${CASK_NAME:-server-box}" +CASK_DISPLAY_NAME="${CASK_DISPLAY_NAME:-ServerBox}" +CASK_DESC="${CASK_DESC:-App for monitoring server status with SSH terminal, SFTP, Container management}" +APP_REPO_SLUG="${APP_REPO_SLUG:-lollipopkit/flutter_server_box}" +TAP_REPO_PATH="${TAP_REPO_PATH:-$HOME/proj/homebrew-taps}" +TAP_CASK_PATH="${TAP_CASK_PATH:-}" +EXPLICIT_TAP_CASK_PATH="${TAP_CASK_PATH:-}" +XCARCHIVE_PATH="${1:-${XCARCHIVE_PATH:-}}" + +if [[ -n "$XCARCHIVE_PATH" ]]; then + APP_PATH="${APP_PATH:-$XCARCHIVE_PATH/Products/Applications/${APP_NAME}.app}" +else + APP_PATH="${APP_PATH:-}" +fi + +if [[ -n "$APP_PATH" ]]; then + INFO_PLIST="$APP_PATH/Contents/Info.plist" +else + INFO_PLIST="${INFO_PLIST:-$REPO_ROOT/macos/Runner/Info.plist}" +fi + +if [[ -f "$INFO_PLIST" ]]; then + APP_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$INFO_PLIST")" + APP_BUILD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$INFO_PLIST")" +else + APP_VERSION="" + APP_BUILD="" +fi + +if [[ -n "$APP_VERSION" && "$APP_VERSION" != '$('* ]]; then + DMG_BASENAME="${DMG_BASENAME:-ServerBox-${APP_VERSION}}" +fi + +if [[ -z "${DMG_PATH:-}" ]]; then + if [[ -z "${DMG_BASENAME:-}" ]]; then + echo "DMG_PATH requires DMG_BASENAME when version is unavailable" >&2 + echo "Provide DMG_PATH directly, or provide XCARCHIVE_PATH/APP_PATH so DMG_BASENAME can be resolved." >&2 + exit 1 + fi + DMG_PATH="$REPO_ROOT/build/artifacts/${DMG_BASENAME}.dmg" +fi + +if [[ ! -f "$DMG_PATH" ]]; then + echo "DMG not found: $DMG_PATH" >&2 + echo "Run package-dmg-from-xcarchive.sh first or provide DMG_PATH." >&2 + exit 1 +fi + +if [[ -z "$APP_VERSION" || "$APP_VERSION" == '$('* ]]; then + dmg_filename="$(basename "$DMG_PATH")" + if [[ "$dmg_filename" =~ ^ServerBox-([0-9]+(\.[0-9]+){1,2})\.dmg$ ]]; then + APP_VERSION="${BASH_REMATCH[1]}" + APP_BUILD="${APP_BUILD:-}" + elif [[ "$dmg_filename" =~ ^ServerBox-([0-9]+(\.[0-9]+){1,2})-([0-9]+)\.dmg$ ]]; then + APP_VERSION="${BASH_REMATCH[1]}" + APP_BUILD="${BASH_REMATCH[3]}" + else + echo "unable to determine version from $DMG_PATH" >&2 + echo "Provide XCARCHIVE_PATH, APP_PATH, or a DMG named ServerBox-.dmg." >&2 + exit 1 + fi +fi + +RELEASE_TAG="${RELEASE_TAG:-v${APP_VERSION}}" +DMG_BASENAME="${DMG_BASENAME:-ServerBox-${APP_VERSION}}" + +if [[ -z "$TAP_CASK_PATH" && -n "$TAP_REPO_PATH" ]]; then + TAP_CASK_PATH="$TAP_REPO_PATH/Casks/${CASK_NAME}.rb" +fi + +if [[ -z "$TAP_CASK_PATH" ]]; then + echo "TAP_REPO_PATH or TAP_CASK_PATH is required" >&2 + exit 1 +fi + +if [[ -z "$EXPLICIT_TAP_CASK_PATH" && -n "$TAP_REPO_PATH" && ! -d "$TAP_REPO_PATH" ]]; then + echo "TAP_REPO_PATH does not exist: $TAP_REPO_PATH" >&2 + exit 1 +fi + +SHA256="$(shasum -a 256 "$DMG_PATH" | awk '{print $1}')" + +mkdir -p "$(dirname "$TAP_CASK_PATH")" +cat > "$TAP_CASK_PATH" <=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="], + + "tailwindcss": ["tailwindcss@4.2.4", "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.2.4.tgz", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + + "tapable": ["tapable@2.3.3", "https://registry.npmmirror.com/tapable/-/tapable-2.3.3.tgz", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "tinyglobby": ["tinyglobby@0.2.16", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tw-animate-css": ["tw-animate-css@1.4.0", "https://registry.npmmirror.com/tw-animate-css/-/tw-animate-css-1.4.0.tgz", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + + "typesafe-i18n": ["typesafe-i18n@5.27.1", "", { "peerDependencies": { "typescript": ">=3.5.1" }, "bin": { "typesafe-i18n": "cli/typesafe-i18n.mjs" } }, "sha512-749uWo2ZXETT//kWjVYPm8QPYR8xLh8G0wLfoAyCAtAmysX67uCaAyLjAjAWojL6fuJpE5B6yIjwvO9orXzUPg=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "vite": ["vite@8.0.10", "https://registry.npmmirror.com/vite/-/vite-8.0.10.tgz", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="], + + "vitefu": ["vitefu@1.1.3", "https://registry.npmmirror.com/vitefu/-/vitefu-1.1.3.tgz", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + + "zimmerframe": ["zimmerframe@1.1.4", "https://registry.npmmirror.com/zimmerframe/-/zimmerframe-1.1.4.tgz", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "https://registry.npmmirror.com/@emnapi/core/-/core-1.10.0.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.10.0.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + } +} diff --git a/website/components.json b/website/components.json new file mode 100644 index 000000000..47fd6335c --- /dev/null +++ b/website/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/app.css", + "baseColor": "taupe" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": false, + "registry": "https://shadcn-svelte.com/registry", + "style": "nova", + "iconLibrary": "lucide", + "menuColor": "default", + "menuAccent": "subtle" +} diff --git a/website/index.html b/website/index.html new file mode 100644 index 000000000..88acd3070 --- /dev/null +++ b/website/index.html @@ -0,0 +1,17 @@ + + + + + + + + ServerBox — Server status, SSH, and operations in one Flutter app + + +
+ + + diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..582feee13 --- /dev/null +++ b/website/package.json @@ -0,0 +1,31 @@ +{ + "name": "serverbox-website", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "typesafe-i18n": "typesafe-i18n --no-watch", + "build": "bun run typesafe-i18n && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@fontsource-variable/figtree": "^5.2.10", + "@lucide/svelte": "^1.11.0", + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tailwindcss/vite": "^4.2.4", + "clsx": "^2.1.1", + "shadcn-svelte": "^1.2.7", + "svelte": "^5.55.5", + "tailwind-merge": "^3.5.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.2.4", + "tw-animate-css": "^1.4.0", + "typescript": "^6.0.3", + "vite": "^8.0.10" + }, + "dependencies": { + "gsap": "^3.15.0", + "typesafe-i18n": "^5.27.1" + } +} diff --git a/website/public/app_icon.png b/website/public/app_icon.png new file mode 100644 index 000000000..2ab34c7a3 Binary files /dev/null and b/website/public/app_icon.png differ diff --git a/website/public/icons.svg b/website/public/icons.svg new file mode 100644 index 000000000..e9522193d --- /dev/null +++ b/website/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/src/App.svelte b/website/src/App.svelte new file mode 100644 index 000000000..20f7fe0ec --- /dev/null +++ b/website/src/App.svelte @@ -0,0 +1,331 @@ + + +{#if locale} +
+ + +
+

{$LL.hero.titlePrefix()}
{$LL.hero.titleSuffix()}

+

+ {$LL.hero.subtitle()} +

+ + + +
+ +
+
+

{$LL.features.title()}

+

+ {$LL.features.subtitle()} +

+
+ +
+ {#each features as feature} +
+
{feature.icon}
+

{$LL.features[feature.key].title()}

+

{$LL.features[feature.key].description()}

+
+ {/each} +
+
+ +
+
+

{$LL.capabilities.title()}

+

+ {$LL.capabilities.subtitle()} +

+
+ +
+ {#each capabilities as item} + {item} + {/each} +
+
+ +
+
+

{$LL.download.title()}

+

+ {$LL.download.subtitle()} +

+
+ +
+ {#each downloadGroups as group} +
+
+

{group.label}

+
+
+ {#each group.sources as source} + {#if source.command} + + {:else} + + {source.label} + + {/if} + {/each} +
+
+ {/each} +
+ +

{$LL.download.note()}

+
+ +
+
+

{$LL.cta.title()}

+

+ {$LL.cta.subtitle()} +

+ +
+
+ + +
+{/if} diff --git a/website/src/app.css b/website/src/app.css new file mode 100644 index 000000000..71b347777 --- /dev/null +++ b/website/src/app.css @@ -0,0 +1,712 @@ +@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;700;800&display=swap'); + +:root { + color-scheme: light; + + --black: #000000; + --near-black: #262626; + --dark-surface: #090909; + --stone: #737373; + --mid-gray: #525252; + --silver: #a3a3a3; + --border-light: #d4d4d4; + --light-gray: #e5e5e5; + --snow: #fafafa; + --white: #ffffff; + --ring-blue: rgba(59, 130, 246, 0.5); + + --radius-container: 12px; + --radius-pill: 9999px; + + --font-display: 'Plus Jakarta Sans', 'SF Pro Rounded', system-ui, -apple-system, sans-serif; + --font-body: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, sans-serif; + --font-mono: ui-monospace, 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', monospace; +} + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + + --black: #f5f5f5; + --near-black: #e5e5e5; + --dark-surface: #0f0f0f; + --stone: #a3a3a3; + --mid-gray: #737373; + --silver: #d4d4d4; + --border-light: #404040; + --light-gray: #262626; + --snow: #171717; + --white: #090909; + --ring-blue: rgba(147, 197, 253, 0.55); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: var(--font-body); + background: var(--white); + color: var(--black); + font-size: 16px; + line-height: 1.5; + letter-spacing: normal; + overflow-x: hidden; +} + +#app { + width: 100%; + max-width: 100%; +} + +[id] { + scroll-margin-top: 6rem; +} + +.site-nav { + position: sticky; + top: 1.5rem; + z-index: 40; + display: flex; + align-items: center; + justify-content: space-between; + width: min(1080px, 92vw); + margin: 0 auto; + padding: 0.65rem 1.25rem; + border-radius: var(--radius-pill); + border: 1px solid var(--light-gray); + background: var(--white); +} + +.brand { + text-decoration: none; + color: var(--black); + font-weight: 700; + font-size: 1.05rem; + letter-spacing: -0.02em; + font-family: var(--font-display); +} + +.site-nav nav { + display: flex; + align-items: center; + gap: 1.75rem; +} + +.site-nav nav a { + text-decoration: none; + color: var(--stone); + font-size: 0.9rem; + font-weight: 400; + transition: color 0.2s ease; +} + +.site-nav nav a:hover { + color: var(--black); +} + +.nav-actions { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.language-switcher { + position: relative; + display: inline-flex; + align-items: center; +} + +.language-switcher select { + appearance: none; + min-width: 8.4rem; + border: 1px solid var(--light-gray); + border-radius: var(--radius-pill); + background: var(--white); + color: var(--near-black); + font: 500 0.86rem var(--font-body); + line-height: 1; + padding: 0.55rem 1.9rem 0.55rem 0.9rem; + cursor: pointer; +} + +.language-switcher::after { + content: ''; + position: absolute; + right: 0.85rem; + width: 0.42rem; + height: 0.42rem; + border-right: 1.5px solid var(--stone); + border-bottom: 1.5px solid var(--stone); + pointer-events: none; + transform: translateY(-0.12rem) rotate(45deg); +} + +.language-switcher select:focus-visible, +.nav-cta:focus-visible, +.site-nav a:focus-visible, +.btn:focus-visible { + outline: 2px solid var(--ring-blue); + outline-offset: 2px; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border: 0; +} + +.nav-cta { + text-decoration: none; + border-radius: var(--radius-pill); + background: var(--black); + color: var(--white); + padding: 0.55rem 1.25rem; + font-weight: 500; + font-size: 0.9rem; + transition: opacity 0.2s ease; +} + +.nav-cta:hover { + opacity: 0.85; +} + +.page-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(5rem, 12vw, 9rem) 0; +} + +.hero { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; + padding: clamp(6rem, 16vw, 12rem) 0 clamp(5rem, 10vw, 7rem); +} + +.hero h1 { + font-family: var(--font-display); + font-size: clamp(2.5rem, 5.5vw, 3.5rem); + font-weight: 700; + line-height: 1.05; + letter-spacing: -0.03em; + color: var(--black); + max-width: 18ch; +} + +.hero-subtitle { + color: var(--stone); + font-size: clamp(1rem, 1.4vw, 1.15rem); + line-height: 1.6; + max-width: 52ch; +} + +.hero-actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.75rem; + margin-top: 0.5rem; +} + +.screenshot-stack { + position: relative; + width: min(1040px, 92vw); + height: clamp(430px, 58vw, 620px); + margin-top: clamp(2rem, 5vw, 4rem); + perspective: 1200px; + outline: none; + --spread: clamp(148px, 24vw, 250px); +} + +.screenshot-stack:focus-visible { + outline: 2px solid var(--ring-blue); + outline-offset: 8px; + border-radius: var(--radius-container); +} + +.screenshot-card { + position: absolute; + left: 50%; + top: 50%; + z-index: var(--z); + width: min(220px, 21vw); + aspect-ratio: 9 / 20; + border-radius: 0; + border: 0; + background: transparent; + box-shadow: 0 18px 44px rgba(0, 0, 0, 0.14); + object-fit: cover; + transform: + translate3d(calc(-50% + var(--base-x) + var(--move-x)), calc(-50% + var(--base-y) + var(--move-y)), 0) + rotateZ(var(--base-r)) + rotateX(var(--tilt-x)) + rotateY(var(--tilt-y)); + transform-style: preserve-3d; + will-change: transform; + transition: + transform 520ms cubic-bezier(0.16, 1, 0.3, 1), + box-shadow 0.2s ease, + opacity 0.2s ease; +} + +.screenshot-stack:hover .screenshot-card { + box-shadow: 0 22px 54px rgba(0, 0, 0, 0.16); + transform: + translate3d(calc(-50% + (var(--hover-slot) * var(--spread)) + var(--move-x)), calc(-50% + var(--move-y)), 0) + rotateZ(var(--hover-r)) + rotateX(var(--tilt-x)) + rotateY(var(--tilt-y)); +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; + border-radius: var(--radius-pill); + padding: 0.625rem 1.5rem; + font-weight: 500; + font-size: 0.95rem; + font-family: var(--font-body); + border: none; + cursor: pointer; + transition: opacity 0.2s ease; +} + +.btn:hover { + opacity: 0.85; +} + +.btn-primary { + background: var(--black); + color: var(--white); +} + +.btn-secondary { + background: var(--light-gray); + color: var(--near-black); +} + +.section-head { + margin-bottom: 2.5rem; +} + +.section-head h2 { + font-family: var(--font-display); + font-size: clamp(1.75rem, 3.2vw, 2.25rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + color: var(--black); + max-width: 28ch; +} + +.section-head p { + margin-top: 0.75rem; + color: var(--stone); + font-size: 1rem; + line-height: 1.6; + max-width: 55ch; +} + +.feature-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.feature-card { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--white); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.6rem; + transition: border-color 0.2s ease; +} + +.feature-card:hover { + border-color: var(--border-light); +} + +.feature-card .icon { + width: 36px; + height: 36px; + border-radius: var(--radius-container); + background: var(--snow); + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; +} + +.feature-card h3 { + font-family: var(--font-display); + font-size: 1rem; + font-weight: 500; + color: var(--black); +} + +.feature-card p { + color: var(--stone); + font-size: 0.88rem; + line-height: 1.55; +} + +.feature-card.wide { + grid-column: span 2; +} + +.protocol-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(4rem, 8vw, 6rem) 0; +} + +.protocol-badges { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-top: 1.5rem; +} + +.protocol-badge { + border-radius: var(--radius-pill); + border: 1px solid var(--light-gray); + background: var(--white); + padding: 0.45rem 1rem; + font-size: 0.85rem; + font-weight: 500; + color: var(--near-black); + font-family: var(--font-mono); + transition: background 0.2s ease; +} + +.protocol-badge:hover { + background: var(--snow); +} + +.code-block { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--snow); + display: grid; + gap: 0.3rem; + padding: 1.25rem 1.5rem; + margin-top: 2rem; + font-family: var(--font-mono); + font-size: 0.88rem; + line-height: 1.6; + color: var(--near-black); + overflow-x: auto; +} + +.code-block .prompt { + color: var(--stone); + font-size: 0.78rem; +} + +.code-block .command { + color: var(--black); + font-weight: 500; +} + +.install-note { + margin-top: 0.9rem; + color: var(--stone); + font-size: 0.92rem; + line-height: 1.6; + max-width: 66ch; +} + +.install-note code { + font-family: var(--font-mono); + color: var(--near-black); +} + +.testimonial-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.testimonial-card { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--white); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.testimonial-card .quote { + font-size: 0.95rem; + line-height: 1.6; + color: var(--near-black); + font-style: italic; +} + +.testimonial-card .author { + margin-top: auto; +} + +.testimonial-card .name { + font-weight: 500; + font-size: 0.88rem; + color: var(--black); +} + +.testimonial-card .role { + font-size: 0.82rem; + color: var(--stone); +} + +.cta-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(4rem, 10vw, 8rem) 0; +} + +.download-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(5rem, 12vw, 9rem) 0; +} + +.download-list { + border-top: 1px solid var(--light-gray); +} + +.download-platform { + display: grid; + grid-template-columns: minmax(130px, 0.45fr) minmax(0, 1.55fr); + align-items: center; + gap: 1rem; + padding: 1rem 0; + border-bottom: 1px solid var(--light-gray); +} + +.download-platform-copy h3 { + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 500; + color: var(--black); +} + +.download-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 0.55rem; +} + +.download-icon-btn { + text-decoration: none; + font: inherit; + border-radius: var(--radius-pill); + border: 1px solid var(--light-gray); + background: var(--white); + color: var(--near-black); + min-height: 42px; + padding: 0.55rem 0.72rem; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.42rem; + cursor: pointer; + transition: + opacity 0.2s ease, + background 0.2s ease, + border-color 0.2s ease; +} + +.download-icon-btn:hover { + background: var(--snow); + border-color: var(--border-light); + opacity: 0.9; +} + +.download-icon-btn span { + font-weight: 500; + font-size: 0.92rem; +} + +.download-note { + margin-top: 1rem; + color: var(--stone); + font-size: 0.92rem; + line-height: 1.6; + max-width: 66ch; +} + +.cta-block { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--white); + padding: clamp(2rem, 4vw, 3.5rem); + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.cta-block h2 { + font-family: var(--font-display); + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + max-width: 22ch; +} + +.cta-block p { + color: var(--stone); + max-width: 48ch; + line-height: 1.6; +} + +.cta-actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.75rem; +} + +.site-footer { + width: min(1080px, 92vw); + margin: 0 auto; + padding: 1.5rem 0 3rem; + border-top: 1px solid var(--light-gray); + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; + color: var(--stone); + font-size: 0.85rem; +} + +.site-footer a { + text-decoration: none; + color: var(--stone); + transition: color 0.2s ease; +} + +.site-footer a:hover { + color: var(--black); +} + +.footer-links { + display: flex; + gap: 1.5rem; +} + +@media (max-width: 768px) { + .site-nav nav { + display: none; + } + + .language-switcher select { + min-width: 7.2rem; + } + + .feature-grid { + grid-template-columns: 1fr; + } + + .feature-card.wide { + grid-column: span 1; + } + + .testimonial-grid { + grid-template-columns: 1fr; + } + + .download-platform { + grid-template-columns: 1fr; + gap: 0.9rem; + } + + .download-actions { + justify-content: flex-start; + } + + .hero h1 { + max-width: none; + } +} + +@media (max-width: 480px) { + .site-nav { + top: 0.75rem; + padding: 0.55rem 1rem; + } + + .language-switcher select { + min-width: 4.5rem; + max-width: 4.5rem; + padding-right: 1.45rem; + overflow: hidden; + text-overflow: ellipsis; + } + + .nav-actions { + gap: 0.4rem; + } + + .nav-cta { + padding-inline: 1rem; + } + + .hero { + padding: clamp(4rem, 10vw, 6rem) 0 clamp(3rem, 6vw, 5rem); + } + + .screenshot-stack { + width: min(100%, 92vw); + height: 390px; + margin-top: 1.75rem; + --spread: clamp(88px, 28vw, 124px); + } + + .screenshot-card { + width: min(104px, 24vw); + } + + .protocol-badges { + gap: 0.4rem; + } + + .download-icon-btn { + min-height: 40px; + padding-inline: 0.68rem; + } + + .site-footer { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/website/src/assets/serverbox/app_icon.png b/website/src/assets/serverbox/app_icon.png new file mode 100644 index 000000000..2ab34c7a3 Binary files /dev/null and b/website/src/assets/serverbox/app_icon.png differ diff --git a/website/src/i18n/en/index.ts b/website/src/i18n/en/index.ts new file mode 100644 index 000000000..becde45e8 --- /dev/null +++ b/website/src/i18n/en/index.ts @@ -0,0 +1,106 @@ +import type { BaseTranslation } from '../i18n-types.js' + +const en: BaseTranslation = { + meta: { + lang: 'en', + title: 'ServerBox — Server status, SSH, and operations in one Flutter app', + description: + 'ServerBox monitors Linux, Unix, and Windows servers with status charts, SSH terminal, SFTP, Docker, process, systemd, S.M.A.R.T, push, widgets, and watchOS support.', + }, + nav: { + features: 'Features', + capabilities: 'Capabilities', + download: 'Download', + docs: 'Docs', + languageLabel: 'Language', + }, + hero: { + titlePrefix: 'Server status,', + titleSuffix: 'right in your pocket.', + subtitle: + 'ServerBox brings charts, SSH terminal, SFTP, Docker, process control, systemd, S.M.A.R.T, push alerts, widgets, and watchOS support into one Flutter app.', + primaryAction: 'Download ServerBox', + secondaryAction: 'Explore features', + }, + screenshots: { + label: 'Interactive ServerBox screenshots', + one: 'ServerBox server overview screenshot', + two: 'ServerBox status chart screenshot', + three: 'ServerBox terminal screenshot', + four: 'ServerBox tools screenshot', + }, + features: { + title: 'One compact workspace for everyday server maintenance.', + subtitle: + 'A focused operational surface with no decorative filler — every block maps to a real maintenance workflow.', + charts: { + title: 'Status Charts', + description: + 'Track CPU, memory, sensors, GPU, network, disk, and host health from dense mobile charts.', + }, + workspace: { + title: 'Cross-Platform Workspace', + description: + 'Use ServerBox across iOS, Android, macOS, Linux, and Windows with one familiar Flutter interface.', + }, + terminal: { + title: 'SSH Terminal and SFTP', + description: + 'Open command-line and file sessions directly from a server card, backed by dartssh2 and xterm.dart.', + }, + native: { + title: 'Native Device Hooks', + description: + 'Biometric auth, message push, home widgets, and watchOS support keep server context nearby.', + }, + platforms: { + title: 'Docker, Process, Systemd', + description: + 'Inspect containers, processes, and services without switching away from your monitoring flow.', + }, + }, + capabilities: { + title: 'All the tools. One app.', + subtitle: + 'ServerBox keeps terminal access, file transfer, service checks, hardware health, and device-native alerts in the same workflow.', + installIosPrompt: '# iOS', + installReleasePrompt: '# Android, Linux, and Windows', + items: { + statusChart: 'Status chart', + sshTerminal: 'SSH Terminal', + sftp: 'SFTP', + docker: 'Docker', + process: 'Process', + systemd: 'Systemd', + smart: 'S.M.A.R.T', + gpu: 'GPU', + sensors: 'Sensors', + push: 'Push', + homeWidget: 'Home Widget', + watchos: 'watchOS', + }, + }, + download: { + title: 'Every platform, every source.', + subtitle: + 'Choose the channel that matches your device and trust model. iOS uses the App Store; macOS uses the App Store or Homebrew; Android, Linux, and Windows also have direct package downloads.', + copied: 'Install command copied', + copyPrompt: 'Copy this install command:', + note: + 'Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers.', + }, + cta: { + title: 'ServerBox is free and open source under AGPLv3.', + subtitle: + 'Install from the App Store, GitHub Releases, F-Droid, OpenAPK, or the project CDN. Only download packages from sources you trust.', + appStoreAction: 'Open App Store', + githubAction: 'Download from GitHub Releases', + }, + footer: { + features: 'Features', + capabilities: 'Capabilities', + releases: 'Releases', + }, +} + +export default en diff --git a/website/src/i18n/es/index.ts b/website/src/i18n/es/index.ts new file mode 100644 index 000000000..4647a690a --- /dev/null +++ b/website/src/i18n/es/index.ts @@ -0,0 +1,106 @@ +import type { Translation } from '../i18n-types.js' + +const es: Translation = { + meta: { + lang: 'es', + title: 'ServerBox — Estado de servidores, SSH y operaciones en una app Flutter', + description: + 'ServerBox monitoriza servidores Linux, Unix y Windows con gráficas, terminal SSH, SFTP, Docker, procesos, systemd, S.M.A.R.T, notificaciones, widgets y watchOS.', + }, + nav: { + features: 'Funciones', + capabilities: 'Capacidades', + download: 'Descargar', + docs: 'Documentación', + languageLabel: 'Idioma', + }, + hero: { + titlePrefix: 'Estado del servidor,', + titleSuffix: 'en tu bolsillo.', + subtitle: + 'ServerBox reúne gráficas, terminal SSH, SFTP, Docker, control de procesos, systemd, S.M.A.R.T, alertas, widgets y watchOS en una sola app Flutter.', + primaryAction: 'Descargar ServerBox', + secondaryAction: 'Ver funciones', + }, + screenshots: { + label: 'Capturas interactivas de ServerBox', + one: 'Captura de vista general de ServerBox', + two: 'Captura de gráficas de ServerBox', + three: 'Captura del terminal de ServerBox', + four: 'Captura de herramientas de ServerBox', + }, + features: { + title: 'Un espacio compacto para el mantenimiento diario.', + subtitle: + 'Una superficie operativa enfocada: cada bloque corresponde a un flujo real de mantenimiento.', + charts: { + title: 'Gráficas de estado', + description: + 'Controla CPU, memoria, sensores, GPU, red, disco y salud del host desde gráficas móviles densas.', + }, + workspace: { + title: 'Espacio multiplataforma', + description: + 'Usa ServerBox en iOS, Android, macOS, Linux y Windows con la misma interfaz Flutter.', + }, + terminal: { + title: 'Terminal SSH y SFTP', + description: + 'Abre sesiones de terminal y archivos desde una tarjeta de servidor, con dartssh2 y xterm.dart.', + }, + native: { + title: 'Integraciones nativas', + description: + 'Autenticación biométrica, notificaciones, widgets y watchOS mantienen cerca el contexto del servidor.', + }, + platforms: { + title: 'Docker, procesos, systemd', + description: + 'Inspecciona contenedores, procesos y servicios sin salir del flujo de monitorización.', + }, + }, + capabilities: { + title: 'Todas las herramientas. Una app.', + subtitle: + 'ServerBox mantiene terminal, transferencia de archivos, servicios, salud de hardware y alertas en el mismo flujo.', + installIosPrompt: '# iOS', + installReleasePrompt: '# Android, Linux y Windows', + items: { + statusChart: 'Gráficas', + sshTerminal: 'Terminal SSH', + sftp: 'SFTP', + docker: 'Docker', + process: 'Procesos', + systemd: 'Systemd', + smart: 'S.M.A.R.T', + gpu: 'GPU', + sensors: 'Sensores', + push: 'Alertas', + homeWidget: 'Widget', + watchos: 'watchOS', + }, + }, + download: { + title: 'Cada plataforma, cada fuente.', + subtitle: + 'Elige el canal adecuado para tu dispositivo. iOS usa App Store; macOS usa App Store o Homebrew; Android, Linux y Windows también tienen paquetes directos.', + copied: 'Comando copiado', + copyPrompt: 'Copia este comando:', + note: + 'Descarga solo desde fuentes de confianza. Para notificaciones del servidor, widgets y monitorización companion, instala ServerBoxMonitor en tus servidores.', + }, + cta: { + title: 'ServerBox es gratis y open source bajo AGPLv3.', + subtitle: + 'Instala desde App Store, GitHub Releases, F-Droid, OpenAPK o el CDN del proyecto.', + appStoreAction: 'Abrir App Store', + githubAction: 'Descargar desde GitHub Releases', + }, + footer: { + features: 'Funciones', + capabilities: 'Capacidades', + releases: 'Versiones', + }, +} + +export default es diff --git a/website/src/i18n/formatters.ts b/website/src/i18n/formatters.ts new file mode 100644 index 000000000..9e0741e47 --- /dev/null +++ b/website/src/i18n/formatters.ts @@ -0,0 +1,11 @@ +import type { FormattersInitializer } from 'typesafe-i18n' +import type { Locales, Formatters } from './i18n-types.js' + +export const initFormatters: FormattersInitializer = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/website/src/i18n/fr/index.ts b/website/src/i18n/fr/index.ts new file mode 100644 index 000000000..630013250 --- /dev/null +++ b/website/src/i18n/fr/index.ts @@ -0,0 +1,106 @@ +import type { Translation } from '../i18n-types.js' + +const fr: Translation = { + meta: { + lang: 'fr', + title: 'ServerBox — État des serveurs, SSH et opérations dans une app Flutter', + description: + 'ServerBox surveille les serveurs Linux, Unix et Windows avec graphiques, terminal SSH, SFTP, Docker, processus, systemd, S.M.A.R.T, notifications, widgets et watchOS.', + }, + nav: { + features: 'Fonctions', + capabilities: 'Capacités', + download: 'Télécharger', + docs: 'Documentation', + languageLabel: 'Langue', + }, + hero: { + titlePrefix: 'État des serveurs,', + titleSuffix: 'dans votre poche.', + subtitle: + 'ServerBox réunit graphiques, terminal SSH, SFTP, Docker, contrôle des processus, systemd, S.M.A.R.T, alertes, widgets et watchOS dans une seule app Flutter.', + primaryAction: 'Télécharger ServerBox', + secondaryAction: 'Voir les fonctions', + }, + screenshots: { + label: 'Captures interactives de ServerBox', + one: 'Capture de la vue serveur ServerBox', + two: 'Capture des graphiques ServerBox', + three: 'Capture du terminal ServerBox', + four: 'Capture des outils ServerBox', + }, + features: { + title: 'Un espace compact pour la maintenance quotidienne.', + subtitle: + 'Une surface opérationnelle dense et directe, où chaque bloc correspond à un vrai flux de maintenance.', + charts: { + title: 'Graphiques d’état', + description: + 'Suivez CPU, mémoire, capteurs, GPU, réseau, disque et santé de l’hôte depuis des graphiques mobiles denses.', + }, + workspace: { + title: 'Espace multiplateforme', + description: + 'Utilisez ServerBox sur iOS, Android, macOS, Linux et Windows avec la même interface Flutter familière.', + }, + terminal: { + title: 'Terminal SSH et SFTP', + description: + 'Ouvrez des sessions terminal et fichiers depuis une carte serveur, avec dartssh2 et xterm.dart.', + }, + native: { + title: 'Intégrations natives', + description: + 'Authentification biométrique, notifications, widgets et watchOS gardent le contexte serveur à portée.', + }, + platforms: { + title: 'Docker, processus, systemd', + description: + 'Inspectez conteneurs, processus et services sans quitter le flux de surveillance.', + }, + }, + capabilities: { + title: 'Tous les outils. Une seule app.', + subtitle: + 'ServerBox garde terminal, transfert de fichiers, services, santé matérielle et alertes dans le même flux.', + installIosPrompt: '# iOS', + installReleasePrompt: '# Android, Linux et Windows', + items: { + statusChart: 'Graphiques', + sshTerminal: 'Terminal SSH', + sftp: 'SFTP', + docker: 'Docker', + process: 'Processus', + systemd: 'Systemd', + smart: 'S.M.A.R.T', + gpu: 'GPU', + sensors: 'Capteurs', + push: 'Notifications', + homeWidget: 'Widget', + watchos: 'watchOS', + }, + }, + download: { + title: 'Toutes les plateformes, toutes les sources.', + subtitle: + 'Choisissez le canal adapté à votre appareil. iOS utilise l’App Store ; macOS utilise l’App Store ou Homebrew ; Android, Linux et Windows ont aussi des paquets directs.', + copied: 'Commande copiée', + copyPrompt: 'Copiez cette commande :', + note: + 'Téléchargez uniquement depuis une source de confiance. Pour les notifications serveur, widgets et surveillance compagnon, installez ServerBoxMonitor sur vos serveurs.', + }, + cta: { + title: 'ServerBox est libre et open source sous AGPLv3.', + subtitle: + 'Installez depuis l’App Store, GitHub Releases, F-Droid, OpenAPK ou le CDN du projet.', + appStoreAction: 'Ouvrir l’App Store', + githubAction: 'Télécharger depuis GitHub Releases', + }, + footer: { + features: 'Fonctions', + capabilities: 'Capacités', + releases: 'Versions', + }, +} + +export default fr diff --git a/website/src/i18n/i18n-svelte.ts b/website/src/i18n/i18n-svelte.ts new file mode 100644 index 000000000..23d478de0 --- /dev/null +++ b/website/src/i18n/i18n-svelte.ts @@ -0,0 +1,12 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initI18nSvelte } from 'typesafe-i18n/svelte' +import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales } from './i18n-util.js' + +const { locale, LL, setLocale } = initI18nSvelte(loadedLocales, loadedFormatters) + +export { locale, LL, setLocale } + +export default LL diff --git a/website/src/i18n/i18n-types.ts b/website/src/i18n/i18n-types.ts new file mode 100644 index 000000000..768026d99 --- /dev/null +++ b/website/src/i18n/i18n-types.ts @@ -0,0 +1,551 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + | 'es' + | 'fr' + | 'it' + | 'ja' + | 'kr' + | 'zh-CN' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + meta: { + /** + * e​n + */ + lang: string + /** + * S​e​r​v​e​r​B​o​x​ ​—​ ​S​e​r​v​e​r​ ​s​t​a​t​u​s​,​ ​S​S​H​,​ ​a​n​d​ ​o​p​e​r​a​t​i​o​n​s​ ​i​n​ ​o​n​e​ ​F​l​u​t​t​e​r​ ​a​p​p + */ + title: string + /** + * S​e​r​v​e​r​B​o​x​ ​m​o​n​i​t​o​r​s​ ​L​i​n​u​x​,​ ​U​n​i​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s​ ​s​e​r​v​e​r​s​ ​w​i​t​h​ ​s​t​a​t​u​s​ ​c​h​a​r​t​s​,​ ​S​S​H​ ​t​e​r​m​i​n​a​l​,​ ​S​F​T​P​,​ ​D​o​c​k​e​r​,​ ​p​r​o​c​e​s​s​,​ ​s​y​s​t​e​m​d​,​ ​S​.​M​.​A​.​R​.​T​,​ ​p​u​s​h​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​w​a​t​c​h​O​S​ ​s​u​p​p​o​r​t​. + */ + description: string + } + nav: { + /** + * F​e​a​t​u​r​e​s + */ + features: string + /** + * C​a​p​a​b​i​l​i​t​i​e​s + */ + capabilities: string + /** + * D​o​w​n​l​o​a​d + */ + download: string + /** + * D​o​c​s + */ + docs: string + /** + * L​a​n​g​u​a​g​e + */ + languageLabel: string + } + hero: { + /** + * S​e​r​v​e​r​ ​s​t​a​t​u​s​, + */ + titlePrefix: string + /** + * r​i​g​h​t​ ​i​n​ ​y​o​u​r​ ​p​o​c​k​e​t​. + */ + titleSuffix: string + /** + * S​e​r​v​e​r​B​o​x​ ​b​r​i​n​g​s​ ​c​h​a​r​t​s​,​ ​S​S​H​ ​t​e​r​m​i​n​a​l​,​ ​S​F​T​P​,​ ​D​o​c​k​e​r​,​ ​p​r​o​c​e​s​s​ ​c​o​n​t​r​o​l​,​ ​s​y​s​t​e​m​d​,​ ​S​.​M​.​A​.​R​.​T​,​ ​p​u​s​h​ ​a​l​e​r​t​s​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​w​a​t​c​h​O​S​ ​s​u​p​p​o​r​t​ ​i​n​t​o​ ​o​n​e​ ​F​l​u​t​t​e​r​ ​a​p​p​. + */ + subtitle: string + /** + * D​o​w​n​l​o​a​d​ ​S​e​r​v​e​r​B​o​x + */ + primaryAction: string + /** + * E​x​p​l​o​r​e​ ​f​e​a​t​u​r​e​s + */ + secondaryAction: string + } + screenshots: { + /** + * I​n​t​e​r​a​c​t​i​v​e​ ​S​e​r​v​e​r​B​o​x​ ​s​c​r​e​e​n​s​h​o​t​s + */ + label: string + /** + * S​e​r​v​e​r​B​o​x​ ​s​e​r​v​e​r​ ​o​v​e​r​v​i​e​w​ ​s​c​r​e​e​n​s​h​o​t + */ + one: string + /** + * S​e​r​v​e​r​B​o​x​ ​s​t​a​t​u​s​ ​c​h​a​r​t​ ​s​c​r​e​e​n​s​h​o​t + */ + two: string + /** + * S​e​r​v​e​r​B​o​x​ ​t​e​r​m​i​n​a​l​ ​s​c​r​e​e​n​s​h​o​t + */ + three: string + /** + * S​e​r​v​e​r​B​o​x​ ​t​o​o​l​s​ ​s​c​r​e​e​n​s​h​o​t + */ + four: string + } + features: { + /** + * O​n​e​ ​c​o​m​p​a​c​t​ ​w​o​r​k​s​p​a​c​e​ ​f​o​r​ ​e​v​e​r​y​d​a​y​ ​s​e​r​v​e​r​ ​m​a​i​n​t​e​n​a​n​c​e​. + */ + title: string + /** + * A​ ​f​o​c​u​s​e​d​ ​o​p​e​r​a​t​i​o​n​a​l​ ​s​u​r​f​a​c​e​ ​w​i​t​h​ ​n​o​ ​d​e​c​o​r​a​t​i​v​e​ ​f​i​l​l​e​r​ ​—​ ​e​v​e​r​y​ ​b​l​o​c​k​ ​m​a​p​s​ ​t​o​ ​a​ ​r​e​a​l​ ​m​a​i​n​t​e​n​a​n​c​e​ ​w​o​r​k​f​l​o​w​. + */ + subtitle: string + charts: { + /** + * S​t​a​t​u​s​ ​C​h​a​r​t​s + */ + title: string + /** + * T​r​a​c​k​ ​C​P​U​,​ ​m​e​m​o​r​y​,​ ​s​e​n​s​o​r​s​,​ ​G​P​U​,​ ​n​e​t​w​o​r​k​,​ ​d​i​s​k​,​ ​a​n​d​ ​h​o​s​t​ ​h​e​a​l​t​h​ ​f​r​o​m​ ​d​e​n​s​e​ ​m​o​b​i​l​e​ ​c​h​a​r​t​s​. + */ + description: string + } + workspace: { + /** + * C​r​o​s​s​-​P​l​a​t​f​o​r​m​ ​W​o​r​k​s​p​a​c​e + */ + title: string + /** + * U​s​e​ ​S​e​r​v​e​r​B​o​x​ ​a​c​r​o​s​s​ ​i​O​S​,​ ​A​n​d​r​o​i​d​,​ ​m​a​c​O​S​,​ ​L​i​n​u​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s​ ​w​i​t​h​ ​o​n​e​ ​f​a​m​i​l​i​a​r​ ​F​l​u​t​t​e​r​ ​i​n​t​e​r​f​a​c​e​. + */ + description: string + } + terminal: { + /** + * S​S​H​ ​T​e​r​m​i​n​a​l​ ​a​n​d​ ​S​F​T​P + */ + title: string + /** + * O​p​e​n​ ​c​o​m​m​a​n​d​-​l​i​n​e​ ​a​n​d​ ​f​i​l​e​ ​s​e​s​s​i​o​n​s​ ​d​i​r​e​c​t​l​y​ ​f​r​o​m​ ​a​ ​s​e​r​v​e​r​ ​c​a​r​d​,​ ​b​a​c​k​e​d​ ​b​y​ ​d​a​r​t​s​s​h​2​ ​a​n​d​ ​x​t​e​r​m​.​d​a​r​t​. + */ + description: string + } + native: { + /** + * N​a​t​i​v​e​ ​D​e​v​i​c​e​ ​H​o​o​k​s + */ + title: string + /** + * B​i​o​m​e​t​r​i​c​ ​a​u​t​h​,​ ​m​e​s​s​a​g​e​ ​p​u​s​h​,​ ​h​o​m​e​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​w​a​t​c​h​O​S​ ​s​u​p​p​o​r​t​ ​k​e​e​p​ ​s​e​r​v​e​r​ ​c​o​n​t​e​x​t​ ​n​e​a​r​b​y​. + */ + description: string + } + platforms: { + /** + * D​o​c​k​e​r​,​ ​P​r​o​c​e​s​s​,​ ​S​y​s​t​e​m​d + */ + title: string + /** + * I​n​s​p​e​c​t​ ​c​o​n​t​a​i​n​e​r​s​,​ ​p​r​o​c​e​s​s​e​s​,​ ​a​n​d​ ​s​e​r​v​i​c​e​s​ ​w​i​t​h​o​u​t​ ​s​w​i​t​c​h​i​n​g​ ​a​w​a​y​ ​f​r​o​m​ ​y​o​u​r​ ​m​o​n​i​t​o​r​i​n​g​ ​f​l​o​w​. + */ + description: string + } + } + capabilities: { + /** + * A​l​l​ ​t​h​e​ ​t​o​o​l​s​.​ ​O​n​e​ ​a​p​p​. + */ + title: string + /** + * S​e​r​v​e​r​B​o​x​ ​k​e​e​p​s​ ​t​e​r​m​i​n​a​l​ ​a​c​c​e​s​s​,​ ​f​i​l​e​ ​t​r​a​n​s​f​e​r​,​ ​s​e​r​v​i​c​e​ ​c​h​e​c​k​s​,​ ​h​a​r​d​w​a​r​e​ ​h​e​a​l​t​h​,​ ​a​n​d​ ​d​e​v​i​c​e​-​n​a​t​i​v​e​ ​a​l​e​r​t​s​ ​i​n​ ​t​h​e​ ​s​a​m​e​ ​w​o​r​k​f​l​o​w​. + */ + subtitle: string + /** + * #​ ​i​O​S + */ + installIosPrompt: string + /** + * #​ ​A​n​d​r​o​i​d​,​ ​L​i​n​u​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s + */ + installReleasePrompt: string + items: { + /** + * S​t​a​t​u​s​ ​c​h​a​r​t + */ + statusChart: string + /** + * S​S​H​ ​T​e​r​m​i​n​a​l + */ + sshTerminal: string + /** + * S​F​T​P + */ + sftp: string + /** + * D​o​c​k​e​r + */ + docker: string + /** + * P​r​o​c​e​s​s + */ + process: string + /** + * S​y​s​t​e​m​d + */ + systemd: string + /** + * S​.​M​.​A​.​R​.​T + */ + smart: string + /** + * G​P​U + */ + gpu: string + /** + * S​e​n​s​o​r​s + */ + sensors: string + /** + * P​u​s​h + */ + push: string + /** + * H​o​m​e​ ​W​i​d​g​e​t + */ + homeWidget: string + /** + * w​a​t​c​h​O​S + */ + watchos: string + } + } + download: { + /** + * E​v​e​r​y​ ​p​l​a​t​f​o​r​m​,​ ​e​v​e​r​y​ ​s​o​u​r​c​e​. + */ + title: string + /** + * C​h​o​o​s​e​ ​t​h​e​ ​c​h​a​n​n​e​l​ ​t​h​a​t​ ​m​a​t​c​h​e​s​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​a​n​d​ ​t​r​u​s​t​ ​m​o​d​e​l​.​ ​i​O​S​ ​u​s​e​s​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​;​ ​m​a​c​O​S​ ​u​s​e​s​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​ ​o​r​ ​H​o​m​e​b​r​e​w​;​ ​A​n​d​r​o​i​d​,​ ​L​i​n​u​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s​ ​a​l​s​o​ ​h​a​v​e​ ​d​i​r​e​c​t​ ​p​a​c​k​a​g​e​ ​d​o​w​n​l​o​a​d​s​. + */ + subtitle: string + /** + * I​n​s​t​a​l​l​ ​c​o​m​m​a​n​d​ ​c​o​p​i​e​d + */ + copied: string + /** + * C​o​p​y​ ​t​h​i​s​ ​i​n​s​t​a​l​l​ ​c​o​m​m​a​n​d​: + */ + copyPrompt: string + /** + * O​n​l​y​ ​d​o​w​n​l​o​a​d​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​a​ ​s​o​u​r​c​e​ ​y​o​u​ ​t​r​u​s​t​.​ ​F​o​r​ ​s​e​r​v​e​r​-​s​i​d​e​ ​p​u​s​h​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​c​o​m​p​a​n​i​o​n​ ​m​o​n​i​t​o​r​i​n​g​,​ ​i​n​s​t​a​l​l​ ​S​e​r​v​e​r​B​o​x​M​o​n​i​t​o​r​ ​s​e​p​a​r​a​t​e​l​y​ ​o​n​ ​y​o​u​r​ ​s​e​r​v​e​r​s​. + */ + note: string + } + cta: { + /** + * S​e​r​v​e​r​B​o​x​ ​i​s​ ​f​r​e​e​ ​a​n​d​ ​o​p​e​n​ ​s​o​u​r​c​e​ ​u​n​d​e​r​ ​A​G​P​L​v​3​. + */ + title: string + /** + * I​n​s​t​a​l​l​ ​f​r​o​m​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​,​ ​G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s​,​ ​F​-​D​r​o​i​d​,​ ​O​p​e​n​A​P​K​,​ ​o​r​ ​t​h​e​ ​p​r​o​j​e​c​t​ ​C​D​N​.​ ​O​n​l​y​ ​d​o​w​n​l​o​a​d​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​s​o​u​r​c​e​s​ ​y​o​u​ ​t​r​u​s​t​. + */ + subtitle: string + /** + * O​p​e​n​ ​A​p​p​ ​S​t​o​r​e + */ + appStoreAction: string + /** + * D​o​w​n​l​o​a​d​ ​f​r​o​m​ ​G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s + */ + githubAction: string + } + footer: { + /** + * F​e​a​t​u​r​e​s + */ + features: string + /** + * C​a​p​a​b​i​l​i​t​i​e​s + */ + capabilities: string + /** + * R​e​l​e​a​s​e​s + */ + releases: string + } +} + +export type TranslationFunctions = { + meta: { + /** + * en + */ + lang: () => LocalizedString + /** + * ServerBox — Server status, SSH, and operations in one Flutter app + */ + title: () => LocalizedString + /** + * ServerBox monitors Linux, Unix, and Windows servers with status charts, SSH terminal, SFTP, Docker, process, systemd, S.M.A.R.T, push, widgets, and watchOS support. + */ + description: () => LocalizedString + } + nav: { + /** + * Features + */ + features: () => LocalizedString + /** + * Capabilities + */ + capabilities: () => LocalizedString + /** + * Download + */ + download: () => LocalizedString + /** + * Docs + */ + docs: () => LocalizedString + /** + * Language + */ + languageLabel: () => LocalizedString + } + hero: { + /** + * Server status, + */ + titlePrefix: () => LocalizedString + /** + * right in your pocket. + */ + titleSuffix: () => LocalizedString + /** + * ServerBox brings charts, SSH terminal, SFTP, Docker, process control, systemd, S.M.A.R.T, push alerts, widgets, and watchOS support into one Flutter app. + */ + subtitle: () => LocalizedString + /** + * Download ServerBox + */ + primaryAction: () => LocalizedString + /** + * Explore features + */ + secondaryAction: () => LocalizedString + } + screenshots: { + /** + * Interactive ServerBox screenshots + */ + label: () => LocalizedString + /** + * ServerBox server overview screenshot + */ + one: () => LocalizedString + /** + * ServerBox status chart screenshot + */ + two: () => LocalizedString + /** + * ServerBox terminal screenshot + */ + three: () => LocalizedString + /** + * ServerBox tools screenshot + */ + four: () => LocalizedString + } + features: { + /** + * One compact workspace for everyday server maintenance. + */ + title: () => LocalizedString + /** + * A focused operational surface with no decorative filler — every block maps to a real maintenance workflow. + */ + subtitle: () => LocalizedString + charts: { + /** + * Status Charts + */ + title: () => LocalizedString + /** + * Track CPU, memory, sensors, GPU, network, disk, and host health from dense mobile charts. + */ + description: () => LocalizedString + } + workspace: { + /** + * Cross-Platform Workspace + */ + title: () => LocalizedString + /** + * Use ServerBox across iOS, Android, macOS, Linux, and Windows with one familiar Flutter interface. + */ + description: () => LocalizedString + } + terminal: { + /** + * SSH Terminal and SFTP + */ + title: () => LocalizedString + /** + * Open command-line and file sessions directly from a server card, backed by dartssh2 and xterm.dart. + */ + description: () => LocalizedString + } + native: { + /** + * Native Device Hooks + */ + title: () => LocalizedString + /** + * Biometric auth, message push, home widgets, and watchOS support keep server context nearby. + */ + description: () => LocalizedString + } + platforms: { + /** + * Docker, Process, Systemd + */ + title: () => LocalizedString + /** + * Inspect containers, processes, and services without switching away from your monitoring flow. + */ + description: () => LocalizedString + } + } + capabilities: { + /** + * All the tools. One app. + */ + title: () => LocalizedString + /** + * ServerBox keeps terminal access, file transfer, service checks, hardware health, and device-native alerts in the same workflow. + */ + subtitle: () => LocalizedString + /** + * # iOS + */ + installIosPrompt: () => LocalizedString + /** + * # Android, Linux, and Windows + */ + installReleasePrompt: () => LocalizedString + items: { + /** + * Status chart + */ + statusChart: () => LocalizedString + /** + * SSH Terminal + */ + sshTerminal: () => LocalizedString + /** + * SFTP + */ + sftp: () => LocalizedString + /** + * Docker + */ + docker: () => LocalizedString + /** + * Process + */ + process: () => LocalizedString + /** + * Systemd + */ + systemd: () => LocalizedString + /** + * S.M.A.R.T + */ + smart: () => LocalizedString + /** + * GPU + */ + gpu: () => LocalizedString + /** + * Sensors + */ + sensors: () => LocalizedString + /** + * Push + */ + push: () => LocalizedString + /** + * Home Widget + */ + homeWidget: () => LocalizedString + /** + * watchOS + */ + watchos: () => LocalizedString + } + } + download: { + /** + * Every platform, every source. + */ + title: () => LocalizedString + /** + * Choose the channel that matches your device and trust model. iOS uses the App Store; macOS uses the App Store or Homebrew; Android, Linux, and Windows also have direct package downloads. + */ + subtitle: () => LocalizedString + /** + * Install command copied + */ + copied: () => LocalizedString + /** + * Copy this install command: + */ + copyPrompt: () => LocalizedString + /** + * Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers. + */ + note: () => LocalizedString + } + cta: { + /** + * ServerBox is free and open source under AGPLv3. + */ + title: () => LocalizedString + /** + * Install from the App Store, GitHub Releases, F-Droid, OpenAPK, or the project CDN. Only download packages from sources you trust. + */ + subtitle: () => LocalizedString + /** + * Open App Store + */ + appStoreAction: () => LocalizedString + /** + * Download from GitHub Releases + */ + githubAction: () => LocalizedString + } + footer: { + /** + * Features + */ + features: () => LocalizedString + /** + * Capabilities + */ + capabilities: () => LocalizedString + /** + * Releases + */ + releases: () => LocalizedString + } +} + +export type Formatters = {} diff --git a/website/src/i18n/i18n-util.async.ts b/website/src/i18n/i18n-util.async.ts new file mode 100644 index 000000000..579320a43 --- /dev/null +++ b/website/src/i18n/i18n-util.async.ts @@ -0,0 +1,32 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters.js' +import type { Locales, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales, locales } from './i18n-util.js' + +const localeTranslationLoaders = { + en: () => import('./en/index.js'), + es: () => import('./es/index.js'), + fr: () => import('./fr/index.js'), + it: () => import('./it/index.js'), + ja: () => import('./ja/index.js'), + kr: () => import('./kr/index.js'), + 'zh-CN': () => import('./zh-CN/index.js'), +} + +const updateDictionary = (locale: Locales, dictionary: Partial): Translations => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +export const importLocaleAsync = async (locale: Locales): Promise => + (await localeTranslationLoaders[locale]()).default as unknown as Translations + +export const loadLocaleAsync = async (locale: Locales): Promise => { + updateDictionary(locale, await importLocaleAsync(locale)) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/website/src/i18n/i18n-util.sync.ts b/website/src/i18n/i18n-util.sync.ts new file mode 100644 index 000000000..5be008ab0 --- /dev/null +++ b/website/src/i18n/i18n-util.sync.ts @@ -0,0 +1,36 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters.js' +import type { Locales, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales, locales } from './i18n-util.js' + +import en from './en/index.js' +import es from './es/index.js' +import fr from './fr/index.js' +import it from './it/index.js' +import ja from './ja/index.js' +import kr from './kr/index.js' +import zh_CN from './zh-CN/index.js' + +const localeTranslations = { + en, + es, + fr, + it, + ja, + kr, + 'zh-CN': zh_CN, +} + +export const loadLocale = (locale: Locales): void => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = localeTranslations[locale] as unknown as Translations + loadFormatters(locale) +} + +export const loadAllLocales = (): void => locales.forEach(loadLocale) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/website/src/i18n/i18n-util.ts b/website/src/i18n/i18n-util.ts new file mode 100644 index 000000000..4dfbee2f9 --- /dev/null +++ b/website/src/i18n/i18n-util.ts @@ -0,0 +1,43 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' +import type { LocaleDetector } from 'typesafe-i18n/detectors' +import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import { initExtendDictionary } from 'typesafe-i18n/utils' +import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types.js' + +export const baseLocale: Locales = 'en' + +export const locales: Locales[] = [ + 'en', + 'es', + 'fr', + 'it', + 'ja', + 'kr', + 'zh-CN' +] + +export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) + +export const loadedLocales: Record = {} as Record + +export const loadedFormatters: Record = {} as Record + +export const extendDictionary = initExtendDictionary() + +export const i18nString = (locale: Locales): TranslateByString => initI18nString(locale, loadedFormatters[locale]) + +export const i18nObject = (locale: Locales): TranslationFunctions => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +export const i18n = (): LocaleTranslationFunctions => + initI18n(loadedLocales, loadedFormatters) + +export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/website/src/i18n/it/index.ts b/website/src/i18n/it/index.ts new file mode 100644 index 000000000..7f19e8c97 --- /dev/null +++ b/website/src/i18n/it/index.ts @@ -0,0 +1,106 @@ +import type { Translation } from '../i18n-types.js' + +const it: Translation = { + meta: { + lang: 'it', + title: 'ServerBox — Stato server, SSH e operazioni in una app Flutter', + description: + 'ServerBox monitora server Linux, Unix e Windows con grafici, terminale SSH, SFTP, Docker, processi, systemd, S.M.A.R.T, notifiche, widget e watchOS.', + }, + nav: { + features: 'Funzioni', + capabilities: 'Capacità', + download: 'Scarica', + docs: 'Documenti', + languageLabel: 'Lingua', + }, + hero: { + titlePrefix: 'Stato server,', + titleSuffix: 'in tasca.', + subtitle: + 'ServerBox riunisce grafici, terminale SSH, SFTP, Docker, processi, systemd, S.M.A.R.T, notifiche, widget e watchOS in una sola app Flutter.', + primaryAction: 'Scarica ServerBox', + secondaryAction: 'Esplora le funzioni', + }, + screenshots: { + label: 'Screenshot interattivi di ServerBox', + one: 'Screenshot panoramica server ServerBox', + two: 'Screenshot grafici ServerBox', + three: 'Screenshot terminale ServerBox', + four: 'Screenshot strumenti ServerBox', + }, + features: { + title: 'Uno spazio compatto per la manutenzione quotidiana.', + subtitle: + 'Una superficie operativa focalizzata: ogni blocco corrisponde a un flusso reale di manutenzione.', + charts: { + title: 'Grafici di stato', + description: + 'Controlla CPU, memoria, sensori, GPU, rete, disco e salute host da grafici mobili densi.', + }, + workspace: { + title: 'Spazio multipiattaforma', + description: + 'Usa ServerBox su iOS, Android, macOS, Linux e Windows con la stessa interfaccia Flutter.', + }, + terminal: { + title: 'Terminale SSH e SFTP', + description: + 'Apri sessioni terminale e file da una scheda server, con dartssh2 e xterm.dart.', + }, + native: { + title: 'Integrazioni native', + description: + 'Autenticazione biometrica, notifiche, widget e watchOS mantengono vicino il contesto server.', + }, + platforms: { + title: 'Docker, processi, systemd', + description: + 'Ispeziona container, processi e servizi senza uscire dal flusso di monitoraggio.', + }, + }, + capabilities: { + title: 'Tutti gli strumenti. Una sola app.', + subtitle: + 'ServerBox tiene terminale, trasferimento file, servizi, salute hardware e avvisi nello stesso flusso.', + installIosPrompt: '# iOS', + installReleasePrompt: '# Android, Linux e Windows', + items: { + statusChart: 'Grafici', + sshTerminal: 'Terminale SSH', + sftp: 'SFTP', + docker: 'Docker', + process: 'Processi', + systemd: 'Systemd', + smart: 'S.M.A.R.T', + gpu: 'GPU', + sensors: 'Sensori', + push: 'Notifiche', + homeWidget: 'Widget', + watchos: 'watchOS', + }, + }, + download: { + title: 'Ogni piattaforma, ogni sorgente.', + subtitle: + 'Scegli il canale adatto al dispositivo. iOS usa App Store; macOS usa App Store o Homebrew; Android, Linux e Windows hanno pacchetti diretti.', + copied: 'Comando copiato', + copyPrompt: 'Copia questo comando:', + note: + 'Scarica solo da fonti attendibili. Per notifiche server, widget e monitoraggio companion, installa ServerBoxMonitor sui server.', + }, + cta: { + title: 'ServerBox è libero e open source sotto AGPLv3.', + subtitle: + 'Installa da App Store, GitHub Releases, F-Droid, OpenAPK o dal CDN del progetto.', + appStoreAction: 'Apri App Store', + githubAction: 'Scarica da GitHub Releases', + }, + footer: { + features: 'Funzioni', + capabilities: 'Capacità', + releases: 'Release', + }, +} + +export default it diff --git a/website/src/i18n/ja/index.ts b/website/src/i18n/ja/index.ts new file mode 100644 index 000000000..26967e868 --- /dev/null +++ b/website/src/i18n/ja/index.ts @@ -0,0 +1,106 @@ +import type { Translation } from '../i18n-types.js' + +const ja: Translation = { + meta: { + lang: 'ja', + title: 'ServerBox — サーバー状態、SSH、運用を 1 つの Flutter アプリで', + description: + 'ServerBox は、状態チャート、SSH ターミナル、SFTP、Docker、プロセス、systemd、S.M.A.R.T、通知、ウィジェット、watchOS で Linux、Unix、Windows サーバーを監視します。', + }, + nav: { + features: '機能', + capabilities: 'ツール', + download: 'ダウンロード', + docs: 'ドキュメント', + languageLabel: '言語', + }, + hero: { + titlePrefix: 'サーバー状態を、', + titleSuffix: 'ポケットの中に。', + subtitle: + 'ServerBox はチャート、SSH ターミナル、SFTP、Docker、プロセス制御、systemd、S.M.A.R.T、通知、ウィジェット、watchOS を 1 つの Flutter アプリにまとめます。', + primaryAction: 'ServerBox をダウンロード', + secondaryAction: '機能を見る', + }, + screenshots: { + label: 'ServerBox のインタラクティブなスクリーンショット', + one: 'ServerBox サーバー概要のスクリーンショット', + two: 'ServerBox 状態チャートのスクリーンショット', + three: 'ServerBox ターミナルのスクリーンショット', + four: 'ServerBox ツールのスクリーンショット', + }, + features: { + title: '日常のサーバーメンテナンスに使える小さな作業場。', + subtitle: + '装飾を抑え、実際のメンテナンス作業に対応する機能だけを集めています。', + charts: { + title: '状態チャート', + description: + 'CPU、メモリ、センサー、GPU、ネットワーク、ディスク、ホスト状態をモバイルチャートで確認できます。', + }, + workspace: { + title: 'クロスプラットフォーム', + description: + 'iOS、Android、macOS、Linux、Windows で同じ Flutter インターフェースを使えます。', + }, + terminal: { + title: 'SSH ターミナルと SFTP', + description: + 'サーバーカードからターミナルとファイルセッションを直接開けます。dartssh2 と xterm.dart を使用しています。', + }, + native: { + title: 'ネイティブ連携', + description: + '生体認証、通知、ホームウィジェット、watchOS により、サーバーの状況を近くに保てます。', + }, + platforms: { + title: 'Docker、プロセス、systemd', + description: + '監視の流れを離れずに、コンテナ、プロセス、サービスを確認できます。', + }, + }, + capabilities: { + title: '必要なツールを 1 つのアプリに。', + subtitle: + 'ServerBox はターミナル、ファイル転送、サービス確認、ハードウェア状態、デバイス通知を同じ流れにまとめます。', + installIosPrompt: '# iOS', + installReleasePrompt: '# Android、Linux、Windows', + items: { + statusChart: '状態チャート', + sshTerminal: 'SSH ターミナル', + sftp: 'SFTP', + docker: 'Docker', + process: 'プロセス', + systemd: 'Systemd', + smart: 'S.M.A.R.T', + gpu: 'GPU', + sensors: 'センサー', + push: '通知', + homeWidget: 'ホームウィジェット', + watchos: 'watchOS', + }, + }, + download: { + title: 'すべてのプラットフォーム、すべての配布元。', + subtitle: + 'デバイスと信頼できる配布元に合わせて選択できます。iOS は App Store、macOS は App Store または Homebrew、Android、Linux、Windows は直接パッケージも利用できます。', + copied: 'インストールコマンドをコピーしました', + copyPrompt: 'このインストールコマンドをコピーしてください:', + note: + '信頼できる配布元からのみダウンロードしてください。サーバー側の通知、ウィジェット、companion 監視には、サーバーに ServerBoxMonitor を別途インストールしてください。', + }, + cta: { + title: 'ServerBox は AGPLv3 の無料オープンソースです。', + subtitle: + 'App Store、GitHub Releases、F-Droid、OpenAPK、またはプロジェクト CDN からインストールできます。', + appStoreAction: 'App Store を開く', + githubAction: 'GitHub Releases からダウンロード', + }, + footer: { + features: '機能', + capabilities: 'ツール', + releases: 'リリース', + }, +} + +export default ja diff --git a/website/src/i18n/kr/index.ts b/website/src/i18n/kr/index.ts new file mode 100644 index 000000000..8b23a3625 --- /dev/null +++ b/website/src/i18n/kr/index.ts @@ -0,0 +1,106 @@ +import type { Translation } from '../i18n-types.js' + +const kr: Translation = { + meta: { + lang: 'ko', + title: 'ServerBox — 서버 상태, SSH, 운영 도구를 하나의 Flutter 앱으로', + description: + 'ServerBox는 상태 차트, SSH 터미널, SFTP, Docker, 프로세스, systemd, S.M.A.R.T, 푸시, 위젯, watchOS로 Linux, Unix, Windows 서버를 모니터링합니다.', + }, + nav: { + features: '기능', + capabilities: '도구', + download: '다운로드', + docs: '문서', + languageLabel: '언어', + }, + hero: { + titlePrefix: '서버 상태를,', + titleSuffix: '손안에서 확인하세요.', + subtitle: + 'ServerBox는 차트, SSH 터미널, SFTP, Docker, 프로세스 제어, systemd, S.M.A.R.T, 푸시 알림, 위젯, watchOS 지원을 하나의 Flutter 앱에 담았습니다.', + primaryAction: 'ServerBox 다운로드', + secondaryAction: '기능 보기', + }, + screenshots: { + label: 'ServerBox 인터랙티브 스크린샷', + one: 'ServerBox 서버 개요 스크린샷', + two: 'ServerBox 상태 차트 스크린샷', + three: 'ServerBox 터미널 스크린샷', + four: 'ServerBox 도구 스크린샷', + }, + features: { + title: '일상적인 서버 관리를 위한 컴팩트한 작업 공간.', + subtitle: + '장식 없이 실제 운영 흐름에 맞춘 밀도 높은 인터페이스입니다.', + charts: { + title: '상태 차트', + description: + 'CPU, 메모리, 센서, GPU, 네트워크, 디스크, 호스트 상태를 모바일 차트로 확인합니다.', + }, + workspace: { + title: '크로스 플랫폼 작업 공간', + description: + 'iOS, Android, macOS, Linux, Windows에서 같은 Flutter 인터페이스를 사용합니다.', + }, + terminal: { + title: 'SSH 터미널과 SFTP', + description: + '서버 카드에서 바로 터미널과 파일 세션을 열 수 있으며 dartssh2와 xterm.dart를 사용합니다.', + }, + native: { + title: '네이티브 기기 기능', + description: + '생체 인증, 푸시 알림, 홈 위젯, watchOS 지원으로 서버 상태를 가까이에 둡니다.', + }, + platforms: { + title: 'Docker, 프로세스, systemd', + description: + '모니터링 흐름을 벗어나지 않고 컨테이너, 프로세스, 서비스를 확인합니다.', + }, + }, + capabilities: { + title: '모든 도구를 하나의 앱에.', + subtitle: + 'ServerBox는 터미널, 파일 전송, 서비스 점검, 하드웨어 상태, 기기 알림을 같은 흐름에 둡니다.', + installIosPrompt: '# iOS', + installReleasePrompt: '# Android, Linux, Windows', + items: { + statusChart: '상태 차트', + sshTerminal: 'SSH 터미널', + sftp: 'SFTP', + docker: 'Docker', + process: '프로세스', + systemd: 'Systemd', + smart: 'S.M.A.R.T', + gpu: 'GPU', + sensors: '센서', + push: '푸시', + homeWidget: '홈 위젯', + watchos: 'watchOS', + }, + }, + download: { + title: '모든 플랫폼, 모든 배포 경로.', + subtitle: + '기기와 신뢰 모델에 맞는 채널을 선택하세요. iOS는 App Store, macOS는 App Store 또는 Homebrew, Android/Linux/Windows는 직접 패키지를 제공합니다.', + copied: '설치 명령을 복사했습니다', + copyPrompt: '이 설치 명령을 복사하세요:', + note: + '신뢰할 수 있는 출처에서만 패키지를 다운로드하세요. 서버 푸시, 위젯, companion 모니터링에는 서버에 ServerBoxMonitor를 별도로 설치하세요.', + }, + cta: { + title: 'ServerBox는 AGPLv3 기반의 무료 오픈소스입니다.', + subtitle: + 'App Store, GitHub Releases, F-Droid, OpenAPK 또는 프로젝트 CDN에서 설치할 수 있습니다.', + appStoreAction: 'App Store 열기', + githubAction: 'GitHub Releases에서 다운로드', + }, + footer: { + features: '기능', + capabilities: '도구', + releases: '릴리스', + }, +} + +export default kr diff --git a/website/src/i18n/zh-CN/index.ts b/website/src/i18n/zh-CN/index.ts new file mode 100644 index 000000000..dc3e35f7b --- /dev/null +++ b/website/src/i18n/zh-CN/index.ts @@ -0,0 +1,105 @@ +import type { Translation } from '../i18n-types.js' + +const zhCN: Translation = { + meta: { + lang: 'zh-CN', + title: 'ServerBox — 服务器状态、SSH 与运维工具', + description: + 'ServerBox 用状态图表、SSH 终端、SFTP、Docker、进程、systemd、S.M.A.R.T、推送、小组件和 watchOS 支持监控 Linux、Unix 与 Windows 服务器。', + }, + nav: { + features: '特性', + capabilities: '能力', + download: '下载', + docs: '文档', + languageLabel: '语言', + }, + hero: { + titlePrefix: '服务器状态,', + titleSuffix: '就在口袋里。', + subtitle: + 'ServerBox 将图表、SSH 终端、SFTP、Docker、进程控制、systemd、S.M.A.R.T、推送提醒、小组件和 watchOS 支持整合到一个 Flutter 应用里。', + primaryAction: '下载 ServerBox', + secondaryAction: '查看特性', + }, + screenshots: { + label: '可交互的 ServerBox 截图', + one: 'ServerBox 服务器概览截图', + two: 'ServerBox 状态图表截图', + three: 'ServerBox 终端截图', + four: 'ServerBox 工具截图', + }, + features: { + title: '一个紧凑工作区,覆盖日常服务器维护。', + subtitle: '能力密度高,没有装饰性填充;每个模块都对应真实维护工作流。', + charts: { + title: '状态图表', + description: + '通过高密度移动端图表跟踪 CPU、内存、传感器、GPU、网络、磁盘和主机健康状态。', + }, + workspace: { + title: '跨平台工作区', + description: + '在 iOS、Android、macOS、Linux 和 Windows 上使用同一个熟悉的 Flutter 界面。', + }, + terminal: { + title: 'SSH 终端与 SFTP', + description: + '从服务器卡片直接打开命令行和文件会话,底层基于 dartssh2 与 xterm.dart。', + }, + native: { + title: '设备原生能力', + description: + '生物认证、消息推送、桌面小组件和 watchOS 支持,让服务器上下文保持贴近。', + }, + platforms: { + title: 'Docker、进程、systemd', + description: + '无需离开监控流程,就能检查容器、进程和服务状态。', + }, + }, + capabilities: { + title: '所有工具,一个应用。', + subtitle: + 'ServerBox 将终端访问、文件传输、服务检查、硬件健康和设备原生提醒放在同一个工作流中。', + installIosPrompt: '# iOS', + installReleasePrompt: '# Android、Linux 与 Windows', + items: { + statusChart: '状态图表', + sshTerminal: 'SSH 终端', + sftp: 'SFTP', + docker: 'Docker', + process: '进程', + systemd: 'Systemd', + smart: 'S.M.A.R.T', + gpu: 'GPU', + sensors: '传感器', + push: '推送', + homeWidget: '桌面小组件', + watchos: 'watchOS', + }, + }, + download: { + title: '所有平台,所有来源。', + subtitle: + '根据设备和信任模型选择下载渠道。iOS 使用 App Store;macOS 使用 App Store 或 Homebrew;Android、Linux 和 Windows 也提供直接安装包。', + copied: '已复制安装命令', + copyPrompt: '复制此安装命令:', + note: + '请只从你信任的来源下载安装包。若需要服务器端推送、小组件和独立监控,请在服务器上单独安装 ServerBoxMonitor。', + }, + cta: { + title: 'ServerBox 是基于 AGPLv3 的免费开源软件。', + subtitle: + '可从 App Store、GitHub Releases、F-Droid、OpenAPK 或项目 CDN 安装。', + appStoreAction: '打开 App Store', + githubAction: '从 GitHub Releases 下载', + }, + footer: { + features: '特性', + capabilities: '能力', + releases: '版本发布', + }, +} + +export default zhCN diff --git a/website/src/lib/Counter.svelte b/website/src/lib/Counter.svelte new file mode 100644 index 000000000..20bf4c97a --- /dev/null +++ b/website/src/lib/Counter.svelte @@ -0,0 +1,5 @@ + + + diff --git a/website/src/lib/i18n.ts b/website/src/lib/i18n.ts new file mode 100644 index 000000000..7ed28db30 --- /dev/null +++ b/website/src/lib/i18n.ts @@ -0,0 +1,62 @@ +import { baseLocale, isLocale, locales as generatedLocales } from '../i18n/i18n-util' +import type { Locales } from '../i18n/i18n-types' + +type LocaleOption = { + code: Locales + label: string +} + +export const defaultLocale = baseLocale + +export const locales: LocaleOption[] = [ + { code: 'en', label: 'English' }, + { code: 'zh-CN', label: '简体中文' }, + { code: 'fr', label: 'Français' }, + { code: 'it', label: 'Italiano' }, + { code: 'kr', label: '한국어' }, + { code: 'ja', label: '日本語' }, + { code: 'es', label: 'Español' }, +].filter((locale) => generatedLocales.includes(locale.code)) + +export const localeStorageKey = 'serverbox.website.locale' + +function readStoredLocale(): string | undefined { + try { + return localStorage.getItem(localeStorageKey) || undefined + } catch { + return undefined + } +} + +export function normalizeLocale(locale: string | null | undefined): Locales { + if (!locale) return defaultLocale + if (isLocale(locale)) return locale + + const lowerLocale = locale.toLowerCase() + if (lowerLocale.startsWith('zh')) return 'zh-CN' + if (lowerLocale.startsWith('fr')) return 'fr' + if (lowerLocale.startsWith('it')) return 'it' + if (lowerLocale.startsWith('ko') || lowerLocale.startsWith('kr')) return 'kr' + if (lowerLocale.startsWith('ja')) return 'ja' + if (lowerLocale.startsWith('es')) return 'es' + if (lowerLocale.startsWith('en')) return 'en' + + return defaultLocale +} + +export function getInitialLocale(): Locales { + const params = new URLSearchParams(window.location.search) + const queryLocale = normalizeLocale(params.get('lang')) + if (params.has('lang')) return queryLocale + + const storedLocale = readStoredLocale() + if (storedLocale) return normalizeLocale(storedLocale) + + return normalizeLocale(navigator.languages?.[0] || navigator.language) +} + +export function syncLocaleToUrl(locale: Locales): void { + const url = new URL(window.location.href) + url.searchParams.set('lang', normalizeLocale(locale)) + window.history.replaceState({}, '', url) +} diff --git a/website/src/lib/utils.ts b/website/src/lib/utils.ts new file mode 100644 index 000000000..b057456de --- /dev/null +++ b/website/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: ClassValue[]): string { + return twMerge(clsx(inputs)) +} diff --git a/website/src/main.ts b/website/src/main.ts new file mode 100644 index 000000000..9ed68c3a0 --- /dev/null +++ b/website/src/main.ts @@ -0,0 +1,15 @@ +import { mount } from 'svelte' +import './app.css' +import App from './App.svelte' + +const target = document.getElementById('app') + +if (!target) { + throw new Error('App mount target #app was not found') +} + +const app = mount(App, { + target, +}) + +export default app diff --git a/website/svelte.config.js b/website/svelte.config.js new file mode 100644 index 000000000..0cf7db32a --- /dev/null +++ b/website/svelte.config.js @@ -0,0 +1,2 @@ +/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ +export default {} diff --git a/website/tsconfig.json b/website/tsconfig.json new file mode 100644 index 000000000..27e3a8064 --- /dev/null +++ b/website/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + "sourceMap": true, + "esModuleInterop": true, + "baseUrl": ".", + "paths": { + "$lib": ["src/lib"], + "$lib/*": ["src/lib/*"] + }, + "types": ["vite/client"], + "skipLibCheck": true + }, + "include": ["src/**/*.d.ts", "src/**/*.svelte", "src/**/*.ts"] +} diff --git a/website/vite.config.js b/website/vite.config.js new file mode 100644 index 000000000..f66a825c7 --- /dev/null +++ b/website/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import tailwindcss from '@tailwindcss/vite' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [tailwindcss(), svelte()], + resolve: { + alias: { + $lib: path.resolve(__dirname, 'src/lib'), + }, + }, +})