mirror of
https://github.com/Dvorinka/Dash.git
synced 2026-06-04 07:22:56 +00:00
🚀 Dash - Homelab Dashboard
A clean, customizable homelab dashboard inspired by CasaOS. Features: - Empty-first dashboard (no demo data) - 3 themes: Light, Dark, CasaOS glassmorphism - Widgets: Clock (multi-timezone), Pi-hole, Memos, Immich, Image - Drag & drop app organization - Grid + list view for apps - Groups with collapse/expand - Proper widget refresh handling - Visual timezone picker - Square app cards with hover effects Stack: Go + Gin + PostgreSQL + Next.js 15 + React 19 + Tailwind CSS + shadcn/ui
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AppEnv string
|
||||
HTTPAddr string
|
||||
DatabaseURL string
|
||||
DataDir string
|
||||
PublicBaseURL string
|
||||
WidgetFetchTimeout time.Duration
|
||||
WidgetCacheTTL time.Duration
|
||||
MaxIconUploadBytes int64
|
||||
AllowedOrigins []string
|
||||
MigrationsDir string
|
||||
}
|
||||
|
||||
func Load() (Config, error) {
|
||||
cfg := Config{
|
||||
AppEnv: env("APP_ENV", "development"),
|
||||
HTTPAddr: env("HTTP_ADDR", ":8080"),
|
||||
DatabaseURL: env("DATABASE_URL", ""),
|
||||
DataDir: env("DATA_DIR", "./data"),
|
||||
PublicBaseURL: env("PUBLIC_BASE_URL", "http://localhost:8080"),
|
||||
WidgetFetchTimeout: 5 * time.Second,
|
||||
WidgetCacheTTL: 60 * time.Second,
|
||||
MaxIconUploadBytes: 524288,
|
||||
AllowedOrigins: splitCSV(env("ALLOWED_ORIGINS", "http://localhost:3000")),
|
||||
MigrationsDir: env("MIGRATIONS_DIR", "../db/migrations"),
|
||||
}
|
||||
|
||||
var err error
|
||||
if raw := os.Getenv("WIDGET_FETCH_TIMEOUT"); raw != "" {
|
||||
cfg.WidgetFetchTimeout, err = time.ParseDuration(raw)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("WIDGET_FETCH_TIMEOUT: %w", err)
|
||||
}
|
||||
}
|
||||
if raw := os.Getenv("WIDGET_CACHE_TTL"); raw != "" {
|
||||
cfg.WidgetCacheTTL, err = time.ParseDuration(raw)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("WIDGET_CACHE_TTL: %w", err)
|
||||
}
|
||||
}
|
||||
if raw := os.Getenv("MAX_ICON_UPLOAD_BYTES"); raw != "" {
|
||||
cfg.MaxIconUploadBytes, err = strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil {
|
||||
return Config{}, fmt.Errorf("MAX_ICON_UPLOAD_BYTES: %w", err)
|
||||
}
|
||||
}
|
||||
if cfg.DatabaseURL == "" {
|
||||
return Config{}, errors.New("DATABASE_URL is required")
|
||||
}
|
||||
if cfg.MaxIconUploadBytes <= 0 {
|
||||
return Config{}, errors.New("MAX_ICON_UPLOAD_BYTES must be positive")
|
||||
}
|
||||
if cfg.WidgetFetchTimeout <= 0 {
|
||||
return Config{}, errors.New("WIDGET_FETCH_TIMEOUT must be positive")
|
||||
}
|
||||
if cfg.WidgetCacheTTL <= 0 {
|
||||
return Config{}, errors.New("WIDGET_CACHE_TTL must be positive")
|
||||
}
|
||||
if _, err := url.ParseRequestURI(cfg.PublicBaseURL); err != nil {
|
||||
return Config{}, fmt.Errorf("PUBLIC_BASE_URL must be absolute URL: %w", err)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c Config) IconDir() string {
|
||||
return strings.TrimRight(c.DataDir, "/") + "/icons"
|
||||
}
|
||||
|
||||
func env(key, fallback string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func splitCSV(raw string) []string {
|
||||
parts := strings.Split(raw, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user