feat(v0.2.0): config UI panel + folder picker + S3 bucket lister/tester

- Panel Reglages dans l'UI (modal)
- Folder picker natif Wails pour dossier local
- ListS3Buckets via aws-cli (profile-aware)
- TestS3Access pour verifier acces bucket
- Config persistee user config dir (cross-OS): %APPDATA%/field-sync ou ~/.config/field-sync
- Header affiche bucket + dest visibles
- Auto-open modal si bucket vide au premier run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Poulpe
2026-04-28 22:08:22 +00:00
parent dcaf5ac020
commit 610ec13b19
3 changed files with 302 additions and 15 deletions

59
app.go
View File

@@ -3,6 +3,8 @@ package main
import (
"context"
"fmt"
"os/exec"
"strings"
"os"
"path/filepath"
"sync"
@@ -66,12 +68,67 @@ func NewApp() *App {
// startup is called by Wails when the app starts
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
a.cfg = config.Load(".env")
a.cfg = config.LoadFromFile()
for i, letter := range []string{"A", "B", "C", "D"} {
a.slots[i] = SlotState{Slot: letter, Status: "idle"}
}
}
// SaveConfig persists config to user config dir and updates in-memory config
func (a *App) SaveConfig(cfg config.Config) error {
if err := config.Save(cfg); err != nil {
return err
}
a.mu.Lock()
a.cfg = cfg
a.mu.Unlock()
return nil
}
// GetConfigPath returns where the config is stored (for display)
func (a *App) GetConfigPath() string {
return config.Path()
}
// PickLocalDest opens native folder picker, returns selected path or "" if cancelled
func (a *App) PickLocalDest() (string, error) {
path, err := runtime.OpenDirectoryDialog(a.ctx, runtime.OpenDialogOptions{
Title: "Dossier de destination locale",
})
return path, err
}
// ListS3Buckets runs `aws s3api list-buckets --profile <p>` returns []string
func (a *App) ListS3Buckets(profile string) ([]string, error) {
args := []string{"s3api", "list-buckets", "--query", "Buckets[].Name", "--output", "text"}
if profile != "" {
args = append(args, "--profile", profile)
}
out, err := exec.Command("aws", args...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("aws error: %s", strings.TrimSpace(string(out)))
}
raw := strings.TrimSpace(string(out))
if raw == "" || raw == "None" {
return []string{}, nil
}
parts := strings.Fields(raw)
return parts, nil
}
// TestS3Access checks bucket reachable: `aws s3 ls s3://<bucket>/ --profile <p>`
func (a *App) TestS3Access(bucket, profile string) error {
args := []string{"s3", "ls", "s3://" + bucket + "/"}
if profile != "" {
args = append(args, "--profile", profile)
}
out, err := exec.Command("aws", args...).CombinedOutput()
if err != nil {
return fmt.Errorf("accès refusé: %s", strings.TrimSpace(string(out)))
}
return nil
}
// ScanCards returns up to 4 detected SD cards
func (a *App) ScanCards() []detect.SDCard {
cards := detect.Detect()