From 11cf9083faabec6ed27615b224b4fdf576f69173 Mon Sep 17 00:00:00 2001 From: NetCell IT Date: Wed, 26 Aug 2026 22:16:44 +0200 Subject: [PATCH] feat: initial Go project scaffold with build/test/release pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Go module: github.com/netcell-it/deklarix - cmd/server/main.go: HTTP entry point with /health endpoint - scripts/build.sh: cross-compile amd64 + arm64 - scripts/test.sh: go vet + race tests + build-check - scripts/release.sh: full release flow (test → build → tag → push) - packaging/DEBIAN: .deb control template - design/enterprise.css: enterprise design system from enconf - CLAUDE.md: complete build/test/release documentation - .claude/settings.local.json: Claude Code permissions --- .gitignore | 30 + .mcp.json | 11 + CLAUDE.md | 192 ++ cmd/server/main.go | 27 + design/enterprise.css | 4398 +++++++++++++++++++++++++++++++++ go.mod | 3 + packaging/DEBIAN/control.tmpl | 7 + scripts/build.sh | 52 + scripts/release.sh | 60 + scripts/test.sh | 27 + 10 files changed, 4807 insertions(+) create mode 100644 .gitignore create mode 100644 .mcp.json create mode 100644 CLAUDE.md create mode 100644 cmd/server/main.go create mode 100644 design/enterprise.css create mode 100644 go.mod create mode 100644 packaging/DEBIAN/control.tmpl create mode 100755 scripts/build.sh create mode 100755 scripts/release.sh create mode 100755 scripts/test.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7644bf2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Build output +dist/ +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test +*.out + +# Go workspace +go.work +go.work.sum + +# Environment +.env +.env.local +*.local + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..c0cd55e --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "architect": { + "type": "stdio", + "command": "node", + "args": [ + "/var/www/architect-center/mcp-proxy.js" + ] + } + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..72896a6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,192 @@ +# Deklarix + +> Projekt für deklarix.de und deklarix.com + +--- + +## Stack + +| Backend | Frontend (geplant) | +|---------|---------------------| +| Go 1.26 | React 19, TypeScript 5.9 | +| net/http (Standard-Library-first) | Vite 8, Tailwind CSS | +| SQLite (geplant) | enterprise.css Design-System (enconf-Pattern) | + +**Pfad:** `/var/www/deklarix` | **Git:** `https://git.netcell-it.de/projekte/deklarix` | **Branch:** `main` + +--- + +## Projektstruktur + +``` +/var/www/deklarix/ +├── cmd/ +│ └── server/ +│ └── main.go # Entry Point +├── internal/ +│ ├── config/ # Konfiguration (env-basiert) +│ ├── handler/ # HTTP Handler +│ └── middleware/ # Auth, Logging, CORS +├── design/ +│ └── enterprise.css # Gemeinsames Design-System (enconf-Basis) +├── packaging/ +│ └── DEBIAN/ +│ └── control.tmpl # .deb Package-Control-Template +├── scripts/ +│ ├── build.sh # Cross-Compile amd64 + arm64 +│ ├── test.sh # Tests + vet + build-check +│ └── release.sh # Vollständiger Release-Prozess +├── go.mod +├── go.sum +└── CLAUDE.md +``` + +--- + +## Go Commands + +```bash +export PATH=$PATH:/usr/local/go/bin # Immer setzen! + +# Entwickeln +go run ./cmd/server/ + +# Tests +./scripts/test.sh +# oder direkt: +go test -race ./... +go vet ./... + +# Build (amd64 + arm64) +./scripts/build.sh 1.0.0 + +# Build (nur amd64) +./scripts/build.sh 1.0.0 amd64 +``` + +--- + +## Build & Release-Prozess + +### Versioning (Semantic Versioning: MAJOR.MINOR.PATCH) +- **MAJOR** — Breaking changes, API-Inkompatibilitäten +- **MINOR** — Neue Features, rückwärtskompatibel +- **PATCH** — Bugfixes + +### Release-Schritte +```bash +# 1. Alle Änderungen committen +git add -p && git commit -m "feat: ..." + +# 2. Release-Skript (macht Tests → Build → Tag → Push) +./scripts/release.sh 1.2.0 + +# Danach liegt in dist/: +# deklarix_1.2.0_amd64 +# deklarix_1.2.0_arm64 +``` + +### Was das Release-Skript tut +1. Prüft: sauberer Git-Status (keine uncommitted changes) +2. Führt `./scripts/test.sh` aus (vet + race tests + build-check) +3. Kompiliert für `linux/amd64` und `linux/arm64` +4. Setzt Git-Tag `v` mit Annotierung +5. Pusht `main` + Tag nach `origin` + +--- + +## Testing-Pattern + +```go +// Datei: internal/handler/health_test.go +package handler_test + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealth(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/health", nil) + w := httptest.NewRecorder() + HealthHandler(w, req) + if w.Code != http.StatusOK { + t.Fatalf("expected 200, got %d", w.Code) + } +} +``` + +- Tests liegen neben dem Code: `handler/foo_test.go` +- Package: `package foo_test` (Black-Box-Test) oder `package foo` (White-Box) +- Race-Detector immer an: `go test -race ./...` +- Tabellenbasierte Tests für mehrere Inputs + +--- + +## Design-System + +Das Frontend folgt dem **Enterprise Light Theme** aus enconf (`design/enterprise.css`). + +- **Primärfarbe:** `#1677ff` (Blau) +- **Sidebar:** Dunkel (`#0B1426` → `#101D33`) mit weißen Icons +- **Body:** `#F8FAFC` Hintergrund, `#334155` Text +- **Font:** Inter (von `/fonts/inter.css` oder Google Fonts) +- **Radius:** 6px / 8px / 10px + +Für neue Frontend-Projekte unter `/var/www/deklarix`: +```bash +# Frontend-Scaffold (wenn benötigt) +npm create vite@latest frontend -- --template react-ts +cd frontend && npm install +# enterprise.css aus design/ einbinden +``` + +--- + +## Domains + +| Domain | Verwendung | +|--------|-----------| +| deklarix.de | Primär | +| deklarix.com | Redirect / International | + +--- + +## Wichtige Hinweise + +### Go PATH +```bash +# Immer setzen — ist nicht im Standard-PATH des Servers +export PATH=$PATH:/usr/local/go/bin +``` + +### Git Push +```bash +git push origin main +# Remote: https://git.netcell-it.de/projekte/deklarix.git +``` + +### Server-Prozess +```bash +# Start (manuell) +PORT=8080 ./dist/deklarix_latest_amd64 & + +# Logs prüfen +journalctl -u deklarix -f +``` + +--- + +## Vor Änderungen + +1. `go vet ./...` — keine Fehler +2. `./scripts/test.sh` — alle Tests grün +3. Bestehenden Code lesen — nicht raten + +## Nach Änderungen + +1. `./scripts/test.sh` → 0 Fehler +2. `./scripts/build.sh ` → erfolgreich +3. Commit mit semantischer Message: `feat:`, `fix:`, `refactor:`, `docs:` +4. Bei Release: `./scripts/release.sh ` diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..d6dc1fb --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" +) + +func main() { + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + addr := ":" + port + + mux := http.NewServeMux() + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{"ok":true}`) + }) + + log.Printf("Deklarix server starting on %s", addr) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Fatal(err) + } +} diff --git a/design/enterprise.css b/design/enterprise.css new file mode 100644 index 0000000..c207c0b --- /dev/null +++ b/design/enterprise.css @@ -0,0 +1,4398 @@ +/* enconf — Enterprise Light Theme v2 (shadcn-inspired) */ + +@import url('/fonts/inter.css'); + +/* ── Design tokens ──────────────────────────────────────────────────────────── */ +:root { + --branding-primary: #1677ff; + --branding-accent: #1677ff; + --radius: 6px; + --radius-md: 8px; + --radius-lg: 10px; + --shadow-sm: 0 1px 2px rgba(0,0,0,0.05); + --shadow: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04); + --shadow-md: 0 4px 12px rgba(0,0,0,0.08); + --shadow-lg: 0 8px 32px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.05); +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + font-weight: 400; + background: #F8FAFC; + color: #334155; + font-size: 13px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + letter-spacing: -0.01em; +} + +/* Global typography */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600 !important; + color: #0F172A !important; + letter-spacing: -0.02em; +} + +.ant-typography { color: #334155; } +/* font-weight for modal/card titles and buttons comes from the theme tokens + (fontWeightStrong: 600, Button.fontWeight: 500) — no CSS override needed. */ +.ant-tabs-tab { font-weight: 500 !important; } +.ant-btn { letter-spacing: -0.01em; } + +/* === LAYOUT === */ +.app-layout { + display: flex; + min-height: 100vh; +} + +/* === SIDEBAR === */ +.sidebar { + width: 240px; + min-height: 100vh; + background: linear-gradient(180deg, #0B1426 0%, #101D33 50%, #0D1829 100%); + display: flex; + flex-direction: column; + position: fixed; + left: 0; + top: 0; + bottom: 0; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: rgba(255,255,255,0.1) transparent; + z-index: 100; +} + +.sidebar::-webkit-scrollbar { width: 4px; } +.sidebar::-webkit-scrollbar-track { background: transparent; } +.sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; } + +.sidebar-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0,0,0,0.4); + z-index: 99; +} + +.sidebar-logo { + padding: 20px 16px; + display: flex; + align-items: center; + gap: 10px; + border-bottom: 1px solid rgba(255,255,255,0.06); +} + +.sidebar-logo-icon { + width: 32px; + height: 32px; + background: linear-gradient(135deg, #1677ff, #1677ff); + border-radius: 8px; + box-shadow: 0 0 12px rgba(0, 212, 170, 0.4); + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + font-weight: 700; + color: white; +} + +.sidebar-logo-text { + font-size: 16px; + font-weight: 600; + color: #ffffff; +} + +.sidebar-section { + padding: 16px 16px 4px; +} + +.sidebar-section-label { + font-size: 10px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1.5px; + color: #475569; + margin-bottom: 4px; + display: flex; + align-items: center; + gap: 6px; +} + +.sidebar-logo-img { + height: 36px; + width: auto; + max-width: 160px; + flex-shrink: 0; + object-fit: contain; +} + +.sidebar-server-badge { + font-size: 9px; + background: rgba(0,212,170,0.2); + color: #1677ff; + padding: 1px 6px; + border-radius: 10px; + font-weight: 600; +} + +.sidebar-menu { + list-style: none; + padding: 4px 8px; +} + +.sidebar-menu-item { + position: relative; + border-radius: 6px; + margin-bottom: 1px; +} + +.sidebar-menu-item a, +.sidebar-menu-item button { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + font-size: 14px; + color: #94A3B8; + text-decoration: none; + border-radius: 6px; + transition: all 0.15s ease; + background: none; + border: none; + cursor: pointer; + width: 100%; +} + +.sidebar-menu-item a:hover, +.sidebar-menu-item button:hover { + background: rgba(255,255,255,0.05); + color: #CBD5E1; +} + +.sidebar-menu-item.active::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 20px; + background: var(--branding-accent, #1677ff); + border-radius: 0 2px 2px 0; + box-shadow: 0 0 8px color-mix(in srgb, var(--branding-accent, #1677ff) 40%, transparent); +} + +.sidebar-menu-item.active a, +.sidebar-menu-item.active button { + background: color-mix(in srgb, var(--branding-accent, #1677ff) 10%, transparent); + color: var(--branding-accent, #1677ff); +} + +.sidebar-version { + margin-top: auto; + padding: 16px; + font-size: 11px; + color: #475569; + text-align: center; + border-top: 1px solid rgba(255,255,255,0.06); +} + +/* === MAIN CONTENT === */ +.main-content { + margin-left: 240px; + flex: 1; + display: flex; + flex-direction: column; + min-height: 100vh; + overflow-x: hidden; + min-width: 0; +} + +/* === HEADER === */ +.header { + height: 56px; + background: #ffffff; + border-bottom: 1px solid #E5E7EB; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px; + position: sticky; + top: 0; + z-index: 50; + backdrop-filter: blur(8px); + background: rgba(255,255,255,0.95); +} + +.header-title { + font-size: 15px; + font-weight: 500; + color: #1E293B; +} + +.header-left { + display: flex; + align-items: center; + gap: 12px; +} + +.header-actions { + display: flex; + align-items: center; + gap: 8px; +} + +.header-menu-toggle { + display: none; + background: none; + border: none; + cursor: pointer; + padding: 4px; + color: #64748B; + font-size: 18px; + line-height: 1; +} + +.header-support-link { + font-size: 12px; + color: #64748B; + margin-left: 12px; + text-decoration: none; +} + +.header-subscription-select { min-width: 180px; max-width: 280px; } +.header-subscription-text { + font-size: 12px; + color: #64748B; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.header-lang-select { width: 110px; } + +.header-help-btn { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + border: 1px solid #E5E7EB; + color: #64748B; + font-size: 16px; + transition: border-color 0.15s, color 0.15s; + text-decoration: none; +} + +.header-help-btn:hover { border-color: #CBD5E1; color: #374151; } + +.header-user-btn { + display: flex; + align-items: center; + gap: 8px; + background: none; + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 5px 10px; + cursor: pointer; + transition: border-color 0.15s; +} + +.header-user-btn:hover { border-color: #CBD5E1; } + +.header-avatar { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + color: #ffffff; + flex-shrink: 0; + background: linear-gradient(135deg, #1677ff, #1677ff); +} + +.header-avatar--impersonating { + background: linear-gradient(135deg, #F59E0B, #EF4444); +} + +.header-user-email { + font-size: 13px; + font-weight: 500; + color: #374151; + max-width: 140px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +/* ── Impersonation banner ──────────────────────────────────────────────────── */ + +.impersonation-banner { + background: #FEF3C7; + border-bottom: 1px solid #FCD34D; + padding: 6px 20px; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 13px; + color: #92400E; +} + +.impersonation-banner__icon { margin-right: 8px; } + +.impersonation-banner__btn { + background: #F59E0B; + color: #fff; + border: none; + border-radius: 6px; + padding: 4px 12px; + font-size: 12px; + font-weight: 600; + cursor: pointer; +} + +/* === CONTENT AREA === */ +.content-area { + padding: 16px; + flex: 1; + overflow-x: hidden; + min-width: 0; +} + +.content-card { + background: #ffffff; + border: 1px solid #E2E8F0; + border-radius: 12px; + padding: 24px; + box-shadow: 0 1px 3px rgba(0,0,0,0.02), 0 1px 2px rgba(0,0,0,0.03); + margin-bottom: 16px; +} + +/* === TABS (Pill-Style) === */ +.tab-bar { + display: flex; + gap: 8px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.tab-item { + padding: 6px 14px; + font-size: 13px; + font-weight: 500; + border: 1.5px solid #E2E8F0; + border-radius: 8px; + color: #64748B; + background: #ffffff; + cursor: pointer; + transition: all 0.15s ease; +} + +.tab-item:hover { + border-color: #CBD5E1; + color: #475569; +} + +.tab-item.active { + border-color: #1677ff; + background: #F0F9FF; + color: #1d4ed8; +} + +/* === STATUS DOTS === */ +.status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + display: inline-block; +} + +.status-dot.online { + background: #1677ff; + box-shadow: 0 0 8px rgba(0, 212, 170, 0.5); +} + +.status-dot.offline { + background: #EF4444; + box-shadow: 0 0 8px rgba(239, 68, 68, 0.5); +} + +/* === BADGES === */ +.badge { + padding: 2px 8px; + border-radius: 20px; + font-size: 11px; + font-weight: 600; +} + +.badge-success { background: rgba(0, 212, 170, 0.1); color: #1677ff; } +.badge-danger { background: rgba(239, 68, 68, 0.1); color: #EF4444; } +.badge-info { background: rgba(14, 165, 233, 0.1); color: #1677ff; } +.badge-warning { background: rgba(245, 158, 11, 0.1); color: #D97706; } + +/* ═══════════════════════════════════════════════════════════════════════════════ + PREMIUM ANT DESIGN OVERRIDES — High-End Enterprise Look + ═══════════════════════════════════════════════════════════════════════════════ */ + +/* ── Tables — shadcn-clean ───────────────────────────────────────────────────── */ +.ant-table { + border-radius: 8px !important; + overflow: hidden; +} + +.ant-table table { + table-layout: auto !important; +} + +.ant-table-thead > tr > th { + white-space: nowrap !important; +} + +.ant-table-tbody > tr > td { + word-break: break-word; +} + +.ant-table-thead > tr > th, +.ant-table-thead > tr > td { + background: #F8FAFC !important; + color: #64748B !important; + font-weight: 500 !important; + font-size: 12px !important; + text-transform: uppercase !important; + letter-spacing: 0.4px !important; + border-bottom: 1px solid #E2E8F0 !important; + padding: 10px 16px !important; +} + +.ant-table-tbody > tr > td { + padding: 12px 16px !important; + border-bottom: 1px solid #F1F5F9 !important; + font-size: 13px !important; + color: #334155 !important; + transition: background 0.1s ease !important; +} + +/* row hover background comes from Table.rowHoverBg token */ + +.ant-table-tbody > tr:last-child > td { + border-bottom: none !important; +} + +/* Scrollable tables on small screens */ +.ant-table-wrapper { + border-radius: 8px !important; + border: 1px solid #E2E8F0 !important; + overflow: hidden !important; +} + +/* ── Modals ──────────────────────────────────────────────────────────────────── */ +.ant-modal { + max-width: calc(100vw - 32px) !important; +} + +.ant-modal .ant-modal-content { + border-radius: 10px !important; + box-shadow: 0 8px 32px rgba(0,0,0,0.10), 0 2px 8px rgba(0,0,0,0.05) !important; + overflow: hidden !important; + border: 1px solid #E2E8F0 !important; +} + +.ant-modal .ant-modal-header { + background: #FFFFFF !important; + border-bottom: 1px solid #E2E8F0 !important; + padding: 16px 24px !important; + margin: 0 !important; +} + +.ant-modal .ant-modal-title { + color: #0F172A !important; + font-weight: 600 !important; + font-size: 15px !important; +} + +.ant-modal .ant-modal-close { + color: #64748B !important; + top: 12px !important; +} + +.ant-modal .ant-modal-close:hover { + color: #0F172A !important; + background: #F1F5F9 !important; + border-radius: 6px !important; +} + +.ant-modal .ant-modal-body { + padding: 20px 24px !important; +} + +.ant-modal .ant-modal-footer { + border-top: 1px solid #F1F5F9 !important; + padding: 12px 24px !important; + background: #FAFAFA !important; +} + +/* ── Drawers ─────────────────────────────────────────────────────────────────── */ +.ant-drawer .ant-drawer-header { + background: #FFFFFF !important; + border-bottom: 1px solid #E2E8F0 !important; + padding: 16px 24px !important; +} + +.ant-drawer .ant-drawer-title { + color: #0F172A !important; + font-weight: 600 !important; + font-size: 15px !important; +} + +.ant-drawer .ant-drawer-close { + color: #64748B !important; +} + +.ant-drawer .ant-drawer-close:hover { + color: #0F172A !important; + background: #F1F5F9 !important; + border-radius: 6px !important; +} + +.ant-drawer .ant-drawer-body { + padding: 20px 24px !important; + background: #FFFFFF !important; +} + +.ant-drawer .ant-drawer-extra .ant-btn-primary { + background: var(--branding-primary, #1677ff) !important; + border: none !important; +} + +/* ── Buttons — flat, clean, shadcn-style ─────────────────────────────────────── */ +.ant-btn-primary { + /* border-radius / font-weight / box-shadow come from Button tokens. + background + darken-on-hover (below) are the design decisions tokens + can't express, so they stay. */ + background: var(--branding-primary, #1677ff) !important; + border: none !important; + color: #fff !important; + transition: opacity 0.15s ease, background 0.15s ease !important; +} + +.ant-btn-primary:hover { + background: color-mix(in srgb, var(--branding-primary, #1677ff) 85%, #000) !important; + color: #fff !important; +} + +.ant-btn-primary:active { + background: color-mix(in srgb, var(--branding-primary, #1677ff) 75%, #000) !important; +} + +/* Ghost buttons */ +.ant-btn-primary.ant-btn-background-ghost, +.ant-btn.ant-btn-primary[class*="ghost"] { + background: transparent !important; + color: var(--branding-primary, #1677ff) !important; + border: 1px solid var(--branding-primary, #1677ff) !important; + box-shadow: none !important; +} + +.ant-btn-primary.ant-btn-background-ghost:hover, +.ant-btn.ant-btn-primary[class*="ghost"]:hover { + background: rgba(14,165,233,0.05) !important; + color: var(--branding-primary, #1677ff) !important; +} + +/* Danger buttons */ +.ant-btn-primary.ant-btn-dangerous { + background: #EF4444 !important; + color: #fff !important; +} + +.ant-btn-primary.ant-btn-dangerous:hover { + background: #DC2626 !important; + color: #fff !important; +} + +/* Default / secondary buttons */ +.ant-btn-default { + border-radius: 6px !important; + border-color: #E2E8F0 !important; + color: #334155 !important; + font-weight: 500 !important; + background: #FFFFFF !important; + box-shadow: none !important; + transition: background 0.15s ease, border-color 0.15s ease !important; +} + +.ant-btn-default:hover { + border-color: #CBD5E1 !important; + background: #F8FAFC !important; + color: #0F172A !important; + box-shadow: none !important; +} + +.ant-btn-default.ant-btn-dangerous { + color: #EF4444 !important; + border-color: #FCA5A5 !important; +} + +.ant-btn-default.ant-btn-dangerous:hover { + border-color: #EF4444 !important; + background: #FFF5F5 !important; +} + +/* Link buttons */ +.ant-btn-link { + color: var(--branding-primary, #1677ff) !important; + font-weight: 500 !important; +} + +.ant-btn-link:hover { + color: color-mix(in srgb, var(--branding-primary, #1677ff) 75%, #000) !important; + background: rgba(14,165,233,0.05) !important; +} + +/* Text buttons */ +.ant-btn-text { + color: #64748B !important; +} + +.ant-btn-text:hover { + background: #F1F5F9 !important; + color: #334155 !important; +} + +/* Ensure button content color */ +.ant-btn-primary span, +.ant-btn-primary .anticon { + color: inherit !important; +} + +/* ── Tags ────────────────────────────────────────────────────────────────────── */ +.ant-tag { + border-radius: 4px !important; + font-weight: 500 !important; + font-size: 11px !important; + padding: 2px 8px !important; + line-height: 18px !important; + margin-inline-end: 4px !important; +} + +/* ── Cards (Ant Design) ──────────────────────────────────────────────────────── */ +.ant-card { + border-radius: 10px !important; + border-color: #E2E8F0 !important; + box-shadow: 0 1px 2px rgba(0,0,0,0.04) !important; + transition: border-color 0.15s ease, box-shadow 0.15s ease !important; +} + +.ant-card:hover { + border-color: #CBD5E1 !important; + box-shadow: 0 2px 8px rgba(0,0,0,0.06) !important; +} + +.ant-card .ant-card-head { + border-bottom: 1px solid #F1F5F9 !important; + padding: 14px 20px !important; + min-height: auto !important; + background: #FFFFFF !important; +} + +.ant-card .ant-card-head-title { + font-size: 14px !important; + font-weight: 600 !important; + color: #0F172A !important; + padding: 0 !important; +} + +.ant-card .ant-card-body { + padding: 16px 20px !important; +} + +/* ── Form Inputs — shadcn style ──────────────────────────────────────────────── */ +.ant-input, +.ant-input-affix-wrapper, +.ant-input-number, +.ant-input-number-affix-wrapper, +.ant-select-selector, +.ant-picker { + border-radius: 6px !important; + border-color: #E2E8F0 !important; + background: #FFFFFF !important; + transition: border-color 0.15s ease, box-shadow 0.15s ease !important; +} + +.ant-input::placeholder, +.ant-input-number-input::placeholder { + color: #94A3B8 !important; +} + +.ant-input:hover, +.ant-input-affix-wrapper:hover, +.ant-input-number:hover, +.ant-select:not(.ant-select-disabled):hover .ant-select-selector, +.ant-picker:hover { + border-color: #CBD5E1 !important; +} + +.ant-input:focus, +.ant-input-focused, +.ant-input-affix-wrapper-focused, +.ant-input-number-focused, +.ant-select-focused .ant-select-selector, +.ant-picker-focused { + border-color: var(--branding-primary, #1677ff) !important; + box-shadow: 0 0 0 2px rgba(14,165,233,0.12) !important; +} + +.ant-form-item-label > label { + font-weight: 500 !important; + font-size: 13px !important; + color: #374151 !important; +} + +.ant-form-item-explain-error { + font-size: 12px !important; + margin-top: 4px !important; +} + +/* ── Tabs (Ant Design) ───────────────────────────────────────────────────────── */ +.ant-tabs .ant-tabs-tab { + font-weight: 500 !important; + color: #64748B !important; + transition: color 0.15s ease !important; + padding: 10px 16px !important; +} + +.ant-tabs .ant-tabs-tab:hover { + color: #334155 !important; +} + +.ant-tabs .ant-tabs-tab-active .ant-tabs-tab-btn { + color: var(--branding-primary, #1677ff) !important; + font-weight: 600 !important; +} + +.ant-tabs .ant-tabs-ink-bar { + background: var(--branding-primary, #1677ff) !important; + height: 2px !important; + border-radius: 2px !important; +} + +.ant-tabs-content-holder { + padding-top: 16px !important; +} + +/* ── Pagination ──────────────────────────────────────────────────────────────── */ +.ant-pagination .ant-pagination-item { + border-radius: 6px !important; + border-color: #E2E8F0 !important; + transition: border-color 0.15s, background 0.15s !important; +} + +.ant-pagination .ant-pagination-item:hover { + border-color: var(--branding-primary, #1677ff) !important; + background: #F0F9FF !important; +} + +.ant-pagination .ant-pagination-item-active { + background: var(--branding-primary, #1677ff) !important; + border-color: var(--branding-primary, #1677ff) !important; +} + +.ant-pagination .ant-pagination-item-active a { + color: #fff !important; +} + +/* ── Switch ──────────────────────────────────────────────────────────────────── */ +.ant-switch-checked { + background: #10B981 !important; +} + +.ant-switch-checked:hover { + background: #059669 !important; +} + +/* ── Progress Bars ───────────────────────────────────────────────────────────── */ +.ant-progress-bg { + border-radius: 4px !important; +} + +.ant-progress-inner { + border-radius: 4px !important; +} + +/* ── Popconfirm ──────────────────────────────────────────────────────────────── */ +.ant-popconfirm .ant-btn-primary { + border-radius: 6px !important; +} + +/* ── Select Dropdown ─────────────────────────────────────────────────────────── */ +.ant-select-dropdown { + border-radius: 10px !important; + box-shadow: 0 8px 32px rgba(0,0,0,0.12) !important; + border: 1px solid #E2E8F0 !important; + padding: 4px !important; +} + +.ant-select-item-option { + border-radius: 6px !important; + margin: 1px 0 !important; +} + +.ant-select-item-option-active { + background: #F0F9FF !important; +} + +.ant-select-item-option-selected { + background: #F0F9FF !important; + font-weight: 600 !important; +} + +/* ── Tooltip ─────────────────────────────────────────────────────────────────── */ +.ant-tooltip-inner { + border-radius: 8px !important; + font-size: 12px !important; + font-weight: 500 !important; +} + +/* ── Message ─────────────────────────────────────────────────────────────────── */ +.ant-message-notice-content { + border-radius: 10px !important; + box-shadow: 0 8px 24px rgba(0,0,0,0.1) !important; + padding: 10px 16px !important; + font-weight: 500 !important; +} + +/* ── Divider ─────────────────────────────────────────────────────────────────── */ +.ant-divider-inner-text { + font-size: 12px !important; + font-weight: 600 !important; + color: #64748B !important; + text-transform: uppercase !important; + letter-spacing: 0.5px !important; +} + +/* ── Badge ───────────────────────────────────────────────────────────────────── */ +.ant-badge-status-dot { + width: 8px !important; + height: 8px !important; +} + +/* ── Alert ───────────────────────────────────────────────────────────────────── */ +.ant-alert { + border-radius: 10px !important; +} + +/* ═══════════════════════════════════════════════════════════════════════════════ + LAYOUT UTILITIES — replace inline styles in page components + ═══════════════════════════════════════════════════════════════════════════════ */ + +/* Flex helpers */ +.flex { display: flex; } +.flex-center { display: flex; align-items: center; } +.flex-between { display: flex; align-items: center; justify-content: space-between; } +.flex-col { display: flex; flex-direction: column; } +.flex-wrap { flex-wrap: wrap; } +.gap-4 { gap: 4px; } +.gap-8 { gap: 8px; } +.gap-12 { gap: 12px; } +.gap-16 { gap: 16px; } + +/* Spacing */ +.mb-0 { margin-bottom: 0; } +.mb-4 { margin-bottom: 4px; } +.mb-8 { margin-bottom: 8px; } +.mb-12 { margin-bottom: 12px; } +.mb-16 { margin-bottom: 16px; } +.mb-20 { margin-bottom: 20px; } +.mt-0 { margin-top: 0; } +.mt-8 { margin-top: 8px; } +.mt-12 { margin-top: 12px; } +.mt-16 { margin-top: 16px; } +.flex-end { display: flex; justify-content: flex-end; } +.justify-end { justify-content: flex-end; } +.text-15 { font-size: 15px; } +.word-break-all { word-break: break-all; } + +/* Text utilities */ +.text-muted { color: #64748B; } +.text-subtle { color: #94A3B8; } +.text-dark { color: #0F172A; } +.text-xs { font-size: 11px; } +.text-sm { font-size: 12px; } +.text-base { font-size: 13px; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.font-mono { font-variant-numeric: tabular-nums; font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; } +.text-upper { text-transform: uppercase; letter-spacing: 0.4px; font-size: 11px; font-weight: 600; color: #64748B; } +.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* Additional utilities for Settings + global use */ +.mb-24 { margin-bottom: 24px; } +.mt-4 { margin-top: 4px; } +.mr-6 { margin-right: 6px; } +.p-24 { padding: 24px; } +.p-40 { padding: 40px; } +.d-block { display: block; } +.w-full { width: 100%; } +.w-200 { width: 200px; } +.max-w-400 { max-width: 400px; } +.max-w-480 { max-width: 480px; } +.max-w-520 { max-width: 520px; } +.max-w-560 { max-width: 560px; } +.p-0 { padding: 0 !important; } +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-primary { color: #1677ff; } +.text-label { font-size: 13px; font-weight: 600; color: #1E293B; display: block; } +.mono-sm { font-family: monospace; font-size: 12px; } +.mono-xs { font-family: monospace; font-size: 11px; } +.mono-base { font-family: monospace; font-size: 13px; } +.card-bordered { border: 1px solid #E5E7EB; border-radius: 8px; } +.card-bordered--lg { border: 1px solid #E5E7EB; border-radius: 10px; } +.btn-primary-blue { background: #1677ff; border-color: #1677ff; } +.spinner-center { text-align: center; padding: 24px; } + +/* Inline icon+text combos */ +.icon-label { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 13px; +} + +/* Muted icon box (used in section headers, stat cards) */ +.icon-box { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + background: #F0F9FF; + border-radius: 6px; + font-size: 14px; + color: var(--branding-primary, #1677ff); + flex-shrink: 0; +} + +.icon-box--sm { + width: 22px; + height: 22px; + font-size: 12px; + border-radius: 5px; +} + +.icon-box--lg { + width: 36px; + height: 36px; + font-size: 17px; + border-radius: 8px; +} + +/* Color variants */ +.icon-box--green { background: #F0FDF4; color: #10B981; } +.icon-box--red { background: #FFF5F5; color: #EF4444; } +.icon-box--orange { background: #FFFBEB; color: #F59E0B; } +.icon-box--purple { background: #F1F5F9; color: #475569; } +.icon-box--teal { background: #EFF6FF; color: #1677ff; } +.icon-box--slate { background: #F8FAFC; color: #64748B; } + +/* Mobile card items (for ProTable mobile views) */ +.mobile-card { + background: #FFFFFF; + border: 1px solid #E2E8F0; + border-radius: 10px; + padding: 14px 16px; + margin-bottom: 8px; +} + +.mobile-card:last-child { + margin-bottom: 0; +} + +.mobile-card__header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.mobile-card__title { + font-size: 13px; + font-weight: 600; + color: #0F172A; +} + +.mobile-card__meta { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 12px; + color: #64748B; +} + +.mobile-card__row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.mobile-card__actions { + border-top: 1px solid #F1F5F9; + padding-top: 8px; +} + +/* Inline status dot */ +.dot { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + background: var(--dot-bg, #94A3B8); +} + +.dot--green { background: #10B981; } +.dot--red { background: #EF4444; } +.dot--orange { background: #F59E0B; } +.dot--blue { background: #3B82F6; } +.dot--gray { background: #94A3B8; } + +/* Empty state */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 48px 24px; + text-align: center; + gap: 8px; + color: #94A3B8; +} + +.empty-state__icon { + font-size: 36px; + margin-bottom: 8px; + opacity: 0.5; +} + +.empty-state__title { + font-size: 14px; + font-weight: 600; + color: #64748B; +} + +.empty-state__desc { + font-size: 13px; + color: #94A3B8; + max-width: 300px; +} + +/* ── AntD Table inside DataCard (flush/no-padding) ───────────────────────────── */ +/* Remove double borders when table is inside data-card */ +.data-card .ant-table-wrapper { + border: none !important; + border-radius: 0 !important; +} + +.data-card .ant-table { + border-radius: 0 !important; +} + +/* ─── AIDashboardWidget ───────────────────────────────────────────────────── */ + +.ai-widget__icon { color: #00D4AA; } + +.ai-widget__setup { + display: flex; + flex-direction: column; + gap: 10px; +} + +.ai-widget__desc { + font-size: 13px; + color: #64748B; + margin: 0; + line-height: 1.6; +} + +.ai-widget__desc--mb { margin: 0 0 12px; } + +.ai-widget__actions { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 8px; +} + +/* ─── LicenseBanner ──────────────────────────────────────────────────────── */ + +.license-modal-desc { + display: block; + margin-bottom: 12px; + font-size: 13px; +} + +.license-banner-text { color: #fff; } + +.license-banner-link { + font-weight: 600; + cursor: pointer; +} + +.license-banner-link--white { + color: #fff; + text-decoration: underline; +} + +.license-banner-link--urgent { color: #92400E; } +.license-banner-link--normal { color: #1d4ed8; } + +.license-alert--flat { border-radius: 0 !important; } +.license-alert--error { border-radius: 0 !important; background: #DC2626 !important; } + +/* ─── RecoveryPhaseBBanner ───────────────────────────────────────────────── */ + +.recovery-alert { border-radius: 0 !important; border: none !important; border-bottom: 1px solid #FBBF24 !important; } + +/* ─── ServerSelector ─────────────────────────────────────────────────────── */ + +.server-selector-select { min-width: 220px; } + +/* ─── QuotaBar ───────────────────────────────────────────────────────────── */ + +.quota-bar__row { display: flex; justify-content: space-between; margin-bottom: 4px; } +.quota-bar__label { font-size: 13px; color: #64748B; } +.quota-bar__value { font-size: 13px; font-weight: 500; color: #1A1A1A; } +.quota-bar__unlimited-symbol { color: #94A3B8; margin-left: 4px; } +.quota-bar__unlimited-track { height: 6px; border-radius: 3px; background: #E2E8F0; } + +/* ─── SectionNav ─────────────────────────────────────────────────────────── */ + +.section-nav { + display: flex; + gap: 8px; + margin-bottom: 20px; + flex-wrap: wrap; +} + +.section-nav__btn { + display: flex; + align-items: center; + gap: 6px; + padding: 7px 14px; + font-size: 13px; + font-weight: 500; + border: 1.5px solid #E2E8F0; + border-radius: 8px; + color: #64748B; + background: #ffffff; + cursor: pointer; + transition: all 0.15s ease; +} + +.section-nav__btn:hover { border-color: #CBD5E1; color: #475569; } + +.section-nav__btn--active { + font-weight: 600; + border-color: #1677ff; + color: #1d4ed8; + background: #F0F9FF; +} + +.section-nav__btn--active:hover { border-color: #1677ff; color: #1d4ed8; } + +/* ─── TwoFABanner ─────────────────────────────────────────────────────────── */ + +.twofabanner-link { font-weight: 600; cursor: pointer; } +.twofabanner-alert { border-radius: 0 !important; } + +/* ─── LogoUpload ──────────────────────────────────────────────────────────── */ + +.logo-upload__wrapper { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } + +.logo-upload__preview { + max-width: 140px; + max-height: 40px; + object-fit: contain; + border: 1px solid #E5E7EB; + border-radius: 6px; + padding: 4px; + background: #fff; +} + +/* ─── PermissionGuard ─────────────────────────────────────────────────────── */ + +.permission-guard-loading { display: flex; justify-content: center; align-items: center; min-height: 240px; } + +/* ═══════════════════════════════════════════════════════════════════════════════ + RESPONSIVE — Mobile Optimization + ═══════════════════════════════════════════════════════════════════════════════ */ + +@media (max-width: 768px) { + .ant-drawer-content-wrapper { + width: 100% !important; + max-width: 100vw !important; + } +} + +@media (max-width: 992px) { + .sidebar { + transform: translateX(-100%); + transition: transform 0.3s ease; + } + + .sidebar.open { + transform: translateX(0); + } + + .main-content { + margin-left: 0; + } + + .header { + height: 52px; + padding: 0 12px; + } + + .header-menu-toggle { + display: flex !important; + } + + .sidebar-overlay { + display: block !important; + } + + .content-area { + padding: 8px; + } + + .content-card { + padding: 14px; + border-radius: 10px; + } + + .impersonation-banner { + flex-wrap: wrap; + gap: 8px; + padding: 8px 12px !important; + font-size: 12px !important; + } + + .ant-table-wrapper { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + .ant-modal .ant-modal-body { + padding: 16px !important; + } + + .ant-modal .ant-modal-header { + padding: 14px 16px !important; + } + + .ant-modal .ant-modal-footer { + padding: 12px 16px !important; + } +} + +@media (max-width: 768px) { + .header-title { + font-size: 13px; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .header-actions { + gap: 6px; + } + + .header-user-email { + display: none !important; + } + + .header-lang-text { + display: none !important; + } + + .content-area { + padding: 6px; + } + + .content-card { + padding: 12px; + border-radius: 8px; + } + + .page-header-icon { + width: 32px !important; + height: 32px !important; + font-size: 15px !important; + } + + .tab-bar { + gap: 4px; + } + + .tab-item { + padding: 5px 10px; + font-size: 12px; + } + + .stat-card { + padding: 12px !important; + } + + .ant-modal { + top: 16px !important; + padding-bottom: 16px !important; + } + + .ant-modal .ant-modal-header { + padding: 12px 16px !important; + } + + .ant-modal .ant-modal-title { + font-size: 14px !important; + } +} + +@media (max-width: 480px) { + .header { + height: 48px; + padding: 0 8px; + } + + .header-title { + max-width: 100px; + font-size: 12px; + } + + .content-area { + padding: 4px; + } + + .content-card { + padding: 8px; + border-radius: 6px; + margin-bottom: 8px; + } +} + +/* ── Auth pages (Login, ForgotPassword, ResetPassword) ───────────────────────── */ +.auth-page { + min-height: 100vh; + background: #F8FAFC; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; +} + +.auth-card { + background: #ffffff; + border: 1px solid #E5E7EB; + border-radius: 10px; + padding: 32px; + width: 100%; + max-width: 400px; + box-shadow: 0 1px 3px rgba(0,0,0,0.03); +} + +.auth-logo-row { + display: flex; + align-items: center; + gap: 12px; + margin-bottom: 28px; +} + +.auth-logo-title { + font-size: 16px; + font-weight: 700; + color: #1E293B; + line-height: 1.2; +} + +.auth-logo-subtitle { + font-size: 12px; + color: #94A3B8; + margin-top: 2px; +} + +.auth-back-link { + margin-top: 20px; + text-align: center; + font-size: 13px; +} + +.auth-hint { + font-size: 13px; + color: #64748B; + margin-bottom: 20px; + line-height: 1.6; +} + +/* ─── Update Changelog ──────────────────────────────────────────────────────── */ + +.changelog-collapse { + margin-top: 8px; + border: 1px solid #FED7AA !important; + border-radius: 8px !important; + background: #FFFBEB !important; +} + +.changelog-collapse-label { + font-weight: 500; + color: #1677ff; +} + +.changelog-body { + margin: 0; + font-family: var(--font-mono, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace); + font-size: 13px; + line-height: 1.7; + white-space: pre-wrap; + word-break: break-word; + color: #1E293B; +} + +.changelog-table .ant-table { + background: transparent; +} + +.changelog-table .ant-table-cell { + padding: 4px 8px !important; + border-bottom: 1px solid #FDE68A !important; + vertical-align: top; +} + +.changelog-version-tag { + font-family: var(--font-mono, 'SFMono-Regular', Consolas, monospace); + font-size: 11px; + color: #64748B; + white-space: nowrap; +} + +.changelog-type-tag { + font-family: var(--font-mono, 'SFMono-Regular', Consolas, monospace); + font-size: 11px; +} +.flex-1 { flex: 1; } +.text-red { color: #EF4444; } +.text-green { color: #10B981; } +.text-success { color: #00D4AA; } +.text-orange { color: #F97316; } +.cursor-pointer { cursor: pointer; } +.min-h-120 { min-height: 120px; } +.ml-6 { margin-left: 6px; } + +/* ═══════════════════════════════════════════════════════════════════════════════ + SHARED ENTERPRISE COMPONENTS — Reusable across all pages + ═══════════════════════════════════════════════════════════════════════════════ */ + +/* ── Settings Panel (light gray section with title) ────────────────────────── */ +.ent-settings-panel { + background: #F8FAFC; + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 16px; +} + +.ent-settings-panel__title { + display: block; + margin-bottom: 12px; + font-size: 13px; + font-weight: 600; + color: #1E293B; +} + +.ent-settings-panel__title .anticon { + margin-right: 6px; +} + +/* ── Info Field (monospace value box with label) ───────────────────────────── */ +.ent-field-label { + display: block; + margin-bottom: 4px; + font-size: 12px; + color: #64748B; + font-weight: 500; +} + +.ent-field-value { + display: block; + font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; + font-size: 13px; + background: #F8FAFC; + padding: 6px 10px; + border-radius: 6px; + border: 1px solid #E2E8F0; + color: #1E293B; + word-break: break-all; +} + +.ent-field-value--success { + background: #F0FDF4; + border-color: #BBF7D0; + color: #15803D; +} + +/* ── Warning / Info Alert Box ──────────────────────────────────────────────── */ +.ent-alert-box { + border-radius: 8px; + padding: 12px 16px; + font-size: 13px; +} + +.ent-alert-box--warning { + background: #FFF7ED; + border: 1px solid #FED7AA; + color: #92400E; +} + +.ent-alert-box--info { + background: #F0F9FF; + border: 1px solid #BAE6FD; + color: #1d4ed8; +} + +.ent-alert-box--success { + background: #F0FDF4; + border: 1px solid #BBF7D0; + color: #15803D; +} + +.ent-alert-box--danger { + background: #FEF2F2; + border: 1px solid #FECACA; + color: #991B1B; +} + +/* ── Inline Card (white bordered card used outside DataCard) ───────────────── */ +.ent-card { + background: #fff; + border: 1px solid #E5E7EB; + border-radius: 10px; + padding: 20px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); +} + +.ent-card--compact { + padding: 16px; +} + +.ent-card--flush { + padding: 0; +} + +/* ── Selectable Card (for type selectors, e.g. restore type) ───────────────── */ +.ent-select-card { + flex: 1 1 calc(50% - 4px); + min-width: 120px; + border: 2px solid #E5E7EB; + border-radius: 8px; + padding: 10px 14px; + cursor: pointer; + background: #FAFAFA; + transition: all 0.15s ease; + display: flex; + align-items: flex-start; + gap: 10px; +} + +.ent-select-card:hover { + border-color: #CBD5E1; +} + +.ent-select-card--active { + border-color: var(--branding-primary, #1677ff); + background: rgba(22, 119, 255, 0.06); +} + +.ent-select-card__icon { + font-size: 20px; + margin-top: 1px; + flex-shrink: 0; +} + +.ent-select-card__title { + font-size: 13px; + font-weight: 600; + color: #1E293B; + line-height: 1.3; +} + +/* ── Code Block (dark terminal-style output) ───────────────────────────────── */ +.ent-code-block { + background: #0B1426; + color: #E2E8F0; + padding: 14px 16px; + border-radius: 8px; + font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; + font-size: 12px; + line-height: 1.7; + max-height: 400px; + overflow-y: auto; + white-space: pre-wrap; + word-break: break-all; +} + +/* ── DS Record / Code Snippet Row ──────────────────────────────────────────── */ +.ent-code-row { + display: flex; + align-items: center; + gap: 6px; + margin-bottom: 4px; +} + +.ent-code-row:last-child { + margin-bottom: 0; +} + +.ent-code-row__code { + flex: 1; + font-size: 11px; + font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; + background: #E5E7EB; + padding: 2px 6px; + border-radius: 4px; + word-break: break-all; +} + +/* ── Mini Stat Chip (inline stat badge used in active tasks, etc.) ─────────── */ +.ent-stat-chip { + background: #F0F9FF; + border: 1px solid #BAE6FD; + border-radius: 8px; + padding: 8px 14px; + display: flex; + align-items: center; + gap: 8px; +} + +.ent-stat-chip--green { + background: #F0FDF4; + border-color: #BBF7D0; +} + +.ent-stat-chip--orange { + background: #FFF7ED; + border-color: #FED7AA; +} + +.ent-stat-chip__icon { + font-size: 16px; + flex-shrink: 0; +} + +.ent-stat-chip__icon--blue { color: #1677ff; } +.ent-stat-chip__icon--green { color: #22C55E; } +.ent-stat-chip__icon--orange { color: #F59E0B; } + +/* ── Divider thin ──────────────────────────────────────────────────────────── */ +.ent-divider-sm { margin: 12px 0; } + +/* ── Additional layout utilities ───────────────────────────────────────────── */ +.flex-justify-center { display: flex; justify-content: center; } +.flex-start { display: flex; align-items: flex-start; } +.mt-2 { margin-top: 2px; } +.ml-8 { margin-left: 8px; } +.ml-12 { margin-left: 12px; } +.pt-16 { padding-top: 16px; } +.text-warning { color: #F59E0B; } +.text-purple { color: #475569; } +.text-slate { color: #475569; } +.line-height-relaxed { line-height: 1.8; } +.m-0 { margin: 0; } + +/* ── Settings — secrets / mono display boxes ───────────────────────────────── */ +/* Read-only mono box (API key, webhook secret) */ +.settings-mono-box { + background: #F8FAFC; + border: 1px solid #E2E8F0; + border-radius: 6px; + padding: 12px 16px; + font-family: monospace; + font-size: 13px; + word-break: break-all; +} + +/* Same as above but content is centered (QR backup code) */ +.settings-mono-box--center { + background: #F8FAFC; + border: 1px solid #E2E8F0; + border-radius: 6px; + padding: 8px 12px; + font-family: monospace; + font-size: 13px; + text-align: center; + word-break: break-all; +} + +/* Webhook delivery response pre block */ +.settings-response-pre { + background: #F8FAFC; + border: 1px solid #E2E8F0; + border-radius: 4px; + padding: 8px; + max-height: 200px; + overflow: auto; + font-size: 11px; + white-space: pre-wrap; + word-break: break-all; +} + +/* ── Settings — reboot confirm content ─────────────────────────────────────── */ +.settings-reboot-warning { + display: flex; + gap: 8px; + align-items: flex-start; + margin-top: 8px; +} + +.settings-reboot-icon { + color: #F59E0B; + font-size: 18px; + flex-shrink: 0; + margin-top: 2px; +} + +/* ── Settings — server provision one-liner ─────────────────────────────────── */ +.settings-provision-cmd { + background: #0B1426; + color: #1677ff; + padding: 12px 16px; + border-radius: 8px; + font-family: monospace; + font-size: 13px; + word-break: break-all; + position: relative; +} + +/* ── Settings — RBL grid ───────────────────────────────────────────────────── */ +.settings-rbl-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 8px; + margin-bottom: 16px; +} + +.settings-rbl-item { + padding: 8px 12px; + border-radius: 8px; +} + +.settings-rbl-custom-item { + padding: 6px 12px; + border: 1px solid #E5E7EB; + border-radius: 8px; +} + +/* ── Settings — mail pill tab bar ──────────────────────────────────────────── */ +/* Reuses global .tab-item / .tab-item.active — no extra class needed */ + +/* ── Settings — section divider row ────────────────────────────────────────── */ +.settings-section-divider { + border-top: 1px solid #E5E7EB; + padding-top: 16px; + margin-bottom: 16px; +} + +.settings-section-divider--no-mb { + border-top: 1px solid #E5E7EB; + padding-top: 16px; +} + +/* ── Settings — license product box ────────────────────────────────────────── */ +.settings-license-box { + margin-bottom: 16px; + padding: 12px 16px; + background: #F0F9FF; + border-radius: 8px; + border: 1px solid #BAE6FD; +} + +.settings-license-title { + font-size: 16px; + font-weight: 600; + color: #1d4ed8; +} + +/* ── Settings — PHP install progress ───────────────────────────────────────── */ +.settings-install-progress { + background: #F0F9FF; + border: 1px solid #BAE6FD; + border-radius: 8px; + padding: 16px; +} + +.settings-install-text { + color: #1d4ed8; +} + +.settings-progress-track { + background: #E0F2FE; + border-radius: 4px; + height: 6px; + overflow: hidden; +} + +.settings-progress-fill { + background: linear-gradient(90deg, #1677ff, #2563EB); + height: 100%; + border-radius: 4px; + transition: width 0.5s ease; + width: var(--fill-width, 0%); +} + +/* ── Settings — section overview grid ─────────────────────────────────────── */ +/* ── Settings — enterprise master-detail layout ────────────────────────────── */ +.settings-layout { + display: grid; + grid-template-columns: 240px 1fr; + gap: 24px; + align-items: start; +} + +.settings-nav { + position: sticky; + top: 16px; + display: flex; + flex-direction: column; + gap: 2px; + padding-right: 8px; + border-right: 1px solid #E5E7EB; +} + +.settings-nav-group { margin-bottom: 10px; } + +.settings-nav-group-label { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #94A3B8; + padding: 6px 10px 4px; +} + +.settings-nav-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 8px 10px; + border: none; + background: transparent; + border-radius: 8px; + cursor: pointer; + color: #475569; + font-size: 13.5px; + text-align: left; + transition: background 0.12s, color 0.12s; +} + +.settings-nav-item:hover { background: #F1F5F9; color: #1E293B; } + +.settings-nav-item.active { + background: #F0F9FF; + color: #0369A1; + font-weight: 600; +} + +.settings-nav-icon { display: inline-flex; font-size: 15px; color: inherit; opacity: 0.9; } +.settings-nav-text { flex: 1; } +.settings-nav-ext { color: #94A3B8; font-size: 12px; } + +.settings-content { min-width: 0; } +.settings-nav-mobile { display: none; } + +@media (max-width: 991px) { + .settings-layout { grid-template-columns: 1fr; gap: 0; } + .settings-nav { display: none; } + .settings-nav-mobile { display: block; } +} + +/* ── Settings — password policy hints ──────────────────────────────────────── */ +.settings-policy-hints { + margin-top: -8px; + line-height: 1.8; +} + +/* ── Settings — Ant Checkbox reset (override AntD left margin) ─────────────── */ +.settings-checkbox-reset { margin-left: 0 !important; font-size: 13px; } + +/* ── Settings — ACME section title spacing ──────────────────────────────────── */ +.settings-acme-title { display: block; margin: 16px 0 8px; } + +/* ── Settings — license key entry section ───────────────────────────────────── */ +.settings-license-key-row { + border-top: 1px solid #F1F5F9; + padding-top: 16px; +} + +/* ── Email page ──────────────────────────────────────────────────────────────── */ + +/* Modal header strip (icon + title row) */ +.email-modal-header { + padding: 20px 24px 16px; + border-bottom: 1px solid #F1F5F9; +} + +/* Modal body padding */ +.email-modal-body { + padding: 16px 24px 8px; +} + +/* Icon inside the colored icon-box in the modal header */ +.email-icon-box-icon { + color: #fff; + font-size: 18px; +} + +/* Title text inside modal header */ +.email-modal-title-text { + font-weight: 600; + font-size: 16px; + color: #1E293B; +} + +/* Quota max-hint "(max. X MB)" next to label */ +.email-quota-hint { + font-size: 11px; + color: #94A3B8; + font-weight: 400; + margin-left: 8px; +} + +/* Row containing the quota bar + usage text + webmail link */ +.email-quota-row { + display: flex; + align-items: center; + gap: 10px; + margin-top: 6px; +} + +/* Quota bar track */ +.email-quota-track { + flex: 1; + max-width: 140px; + height: 4px; + background: #E5E7EB; + border-radius: 2px; + overflow: hidden; +} + +/* Quota bar track without max-width (mobile card) */ +.email-quota-track--full { + flex: 1; + height: 4px; + background: #E5E7EB; + border-radius: 2px; + overflow: hidden; +} + +/* Quota bar fill — width and background are always dynamic (inline style) */ +.email-quota-fill { + height: 100%; + border-radius: 2px; + transition: width 0.3s ease; +} + +/* "Auto" tag on autoresponder-enabled mailboxes */ +.email-tag-auto.ant-tag { + font-size: 10px; + line-height: 16px; + margin: 0; + padding: 0 5px; +} + +/* Mobile-only: "Auto" tag row spacing */ +.email-tag-auto--mb.ant-tag { + font-size: 10px; + margin-bottom: 8px; +} + +/* Webmail inline link-button */ +.email-webmail-btn { + font-size: 11px; + color: #1677ff; + background: none; + border: none; + padding: 0; + cursor: pointer; +} + +/* Secondary cell text (#374151, one step lighter than text-dark) */ +.email-cell-secondary { + font-size: 13px; + color: #374151; +} + +/* Mobile card cell: min-width:0 + flex:1 for proper ellipsis */ +.email-mobile-cell { + min-width: 0; + flex: 1; +} + +/* Mobile quota row (with bottom margin) */ +.email-quota-row-mb { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 8px; +} + +/* IMAP migration: target mailbox info banner */ +.email-migrate-banner { + margin-bottom: 16px; + padding: 10px 14px; + background: #F0F9FF; + border-radius: 8px; + border: 1px solid #BAE6FD; +} + +/* Highlighted email address inside the migrate banner */ +.email-migrate-email { + font-weight: 600; + color: #0369A1; +} + +/* Import counter (large number + "E-Mails found") */ +.email-migrate-count { + margin-top: 16px; + font-weight: 600; + color: #0369A1; + font-size: 22px; +} + +/* "Connecting…" status line */ +.email-migrate-connecting { + margin-top: 16px; + font-weight: 500; + color: #0369A1; +} + +/* Done state: success count */ +.email-migrate-count--done { + margin-top: 12px; + font-weight: 600; + color: #1E293B; + font-size: 22px; +} + +/* Active-import status row (pulsing dot + text) */ +.email-migrate-status-row { + margin-top: 12px; + font-size: 12px; + color: #94A3B8; + display: flex; + align-items: center; + justify-content: center; + gap: 6px; +} + +/* Animated green pulse dot */ +.email-pulse-dot { + display: inline-block; + width: 6px; + height: 6px; + border-radius: 50%; + background: #10B981; + animation: pulse 1.5s infinite; +} + +/* Success checkmark icon */ +.email-migrate-success-icon { + font-size: 48px; + color: #10B981; +} + +/* Migrate form input grid */ +.email-migrate-grid { + display: grid; + gap: 12px; +} + +/* Additional spacing */ +.mb-32 { margin-bottom: 32px; } +.mr-8 { margin-right: 8px; } + +/* Spam settings — section label (block, colored) */ +.email-section-label { + display: block; + color: #374151; + margin-bottom: 12px; +} + +/* Spam settings — slider wrapper with horizontal padding */ +.email-slider-wrap { + padding: 0 8px; +} + +/* Spam settings — switch description indented under toggle */ +.email-switch-desc { + margin: 4px 0 0 48px; +} + +/* Utility: #374151 text color (between slate and dark) */ +.text-gray-700 { color: #374151; } + +/* ── WordPress page utilities ────────────────────────────────────── */ + +/* Spacing */ +.mr-4 { margin-right: 4px; } +.mt-10 { margin-top: 10px; } + +/* Layout */ +.min-w-0 { min-width: 0; } +.min-w-220 { min-width: 220px; } +.items-end { align-items: flex-end; } + +/* Staging badge tag (tiny purple, compact padding) */ +.wp-tag--staging { + font-size: 10px; + padding: 0 5px; + margin: 0; +} + +/* Stat-card icon circle */ +.wp-stat-icon { + width: 36px; + height: 36px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + flex-shrink: 0; +} + +/* Action cards (DB / Security tab centered cards with border) */ +.wp-action-card { + text-align: center; + border-color: #E5E7EB !important; +} + +/* Action-card icon (large icon above label) */ +.wp-action-icon { + font-size: 28px; + margin-bottom: 8px; + display: block; +} +.wp-action-icon--blue { color: #1677ff; } +.wp-action-icon--amber { color: #F59E0B; } +.wp-action-icon--red { color: #EF4444; } + +/* Staging section divider with tight margin */ +.wp-staging-divider.ant-divider { margin: 12px 0 8px 0; } + +/* Staging empty state */ +.wp-staging-empty { + text-align: center; + padding: 8px 0; +} + +/* Staging env list */ +.wp-staging-list { + display: flex; + flex-direction: column; + gap: 8px; +} + +/* Individual staging env row */ +.wp-staging-env { + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 10px 12px; + background: #F9FAFB; +} + +/* Staging env link */ +.wp-staging-link { + font-size: 13px; + font-weight: 500; +} + +/* Staging env date */ +.wp-staging-date { + font-size: 11px; + display: block; + margin-top: 2px; +} + +/* Plugin install form: slug label */ +.wp-slug-label { + display: block; + margin-bottom: 4px; +} + +/* Password input in reset-password modal */ +.wp-mono-input { + font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; + margin-top: 8px; +} + +/* Staging-banner link */ +.wp-staging-banner-link { + font-weight: 600; +} + +/* Badge with custom red background (CVE count in tab label) */ +.wp-badge--red.ant-badge .ant-badge-count { background-color: #EF4444; } + +/* Badge with custom amber background (update count in list) */ +.wp-badge--amber.ant-badge .ant-badge-count { background-color: #F59E0B; } + +/* Search bar in list header */ +.wp-search-input { + max-width: 340px; + flex: 1; +} + +/* List view: clickable domain link */ +.wp-list-domain-link { + font-weight: 600; + color: #1677ff; + cursor: pointer; +} + +/* List view: site title subtitle */ +.wp-list-site-title { + font-size: 12px; + color: #64748B; +} + +/* Spinner center wrapper in cache tab */ +.wp-cache-spinner { + text-align: center; + padding: 24px; +} + +/* Detail header: site title line */ +.wp-detail-site-title { + margin-top: 2px; + display: block; +} + +/* ─── Joomla page BEM classes ─────────────────────────────────────────────── */ + +/* Status dot — geometry only; background/boxShadow remain inline (dynamic color) */ +.joomla-status-dot { + display: inline-block; + width: 7px; + height: 7px; + border-radius: 50%; +} + +/* Stat-card icon wrapper — background/color remain inline (dynamic per card) */ +.joomla-stat-icon { + width: 36px; + height: 36px; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + flex-shrink: 0; +} + +/* Staging badge tag (small pill on ) */ +.joomla-tag--staging { font-size: 10px; padding: 0 5px; margin: 0; } + +/* Password input in reset-password modal */ +.joomla-modal-pw { font-family: monospace; margin-top: 8px; } + +/* Staging banner "back to live" link */ +.joomla-staging-banner-link { font-weight: 600; } + +/* Core-up-to-date tag — left margin */ +.joomla-tag-ml { margin-left: 8px; } + +/* Staging section Divider with tight spacing */ +.joomla-staging-divider.ant-divider { margin: 12px 0 8px 0; } + +/* Staging empty state */ +.joomla-staging-empty { text-align: center; padding: 8px 0; } + +/* Staging env list */ +.joomla-staging-list { display: flex; flex-direction: column; gap: 8px; } + +/* Individual staging env card */ +.joomla-staging-item { + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 10px 12px; + background: #F9FAFB; +} + +/* Staging item flex row (wrap support) */ +.joomla-staging-item__row { flex-wrap: wrap; gap: 6px; } + +/* Staging item meta column (overflow guard) */ +.joomla-staging-item__meta { min-width: 0; } + +/* Staging env URL anchor */ +.joomla-staging-link { font-size: 13px; font-weight: 500; } + +/* Staging env date text */ +.joomla-staging-date { font-size: 11px; display: block; margin-top: 2px; } + +/* Staging "Create Staging" button (full-width + top margin) */ +.joomla-staging-create-btn { width: 100%; margin-top: 10px; } + +/* Extension install form row (aligns bottom) */ +.joomla-ext-install-row { align-items: flex-end; } + +/* Extension install URL field */ +.joomla-ext-url-col { flex: 1; min-width: 220px; } + +/* Extension install type selector */ +.joomla-ext-type-col { min-width: 140px; } + +/* Label above install fields */ +.joomla-ext-field-label { display: block; margin-bottom: 4px; } + +/* DB/security action-card icon (blue) */ +.joomla-card-icon { font-size: 28px; color: #1677ff; margin-bottom: 8px; } + +/* DB/security action-card icon — amber variant */ +.joomla-card-icon--amber { font-size: 28px; color: #F59E0B; margin-bottom: 8px; display: block; } + +/* Action card — centered text + standard border */ +.joomla-action-card { text-align: center; border-color: #E5E7EB !important; } + +/* Detail header: site title secondary line */ +.joomla-detail-site-title { margin-top: 2px; display: block; } + +/* Search bar row */ +.joomla-search-bar { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; } + +/* Search input */ +.joomla-search-input { max-width: 340px; flex: 1; } + +/* List view: domain link */ +.joomla-list-link { font-weight: 600; color: #1677ff; cursor: pointer; } + +/* List view: site title subtitle */ +.joomla-list-subtitle { font-size: 12px; color: #64748B; } + +/* Badge amber background (extension updates in list view) */ +.joomla-badge--amber.ant-badge .ant-badge-count { background-color: #F59E0B; } + +/* ── Backup page ─────────────────────────────────────────────────────────────── */ + +/* Job table: timestamp primary line */ +.backup-job-time { font-size: 13px; line-height: 1.4; } + +/* Job table: schedule name (truncated) */ +.backup-job-name { font-size: 13px; line-height: 1.4; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 220px; } + +/* Job table: compact xs tag (scope / restore-type) */ +.backup-tag-xs { font-size: 10px !important; padding: 0 5px !important; margin: 0 !important; line-height: 16px !important; border: none !important; } + +/* Job table: inline error text */ +.backup-error-text { font-size: 10px; margin-left: 2px; } + +/* Job table: size column value */ +.backup-size-text { font-size: 12px; font-weight: 500; color: #334155; } + +/* Job table: snapshot id (monospace, muted) */ +.backup-snapshot-id { font-family: monospace; font-size: 11px; color: #64748B; } + +/* Job table detail button: muted icon */ +.backup-icon-muted { color: #CBD5E1; } + +/* Icon colours (folder / file) shared across Backup and Browse */ +.icon-folder { color: #FAAD14; } +.icon-file { color: #8C8C8C; } +.icon-folder-lg { color: #FAAD14; font-size: 16px; } +.icon-key { color: #F59E0B; } + +/* Active job card: info border */ +.backup-active-job-card { border: 1px solid #E0F2FE; } + +/* Mobile jobs list: timestamp */ +.backup-job-time-mobile { font-size: 13px; color: #1E293B; } + +/* Mobile jobs list: schedule name secondary line */ +.backup-sched-name { font-size: 12px; color: #475569; } + +/* Mobile jobs list: scope tag with left margin */ +.backup-scope-inline { font-size: 11px; margin-left: 6px; } + +/* File-restore: browse button (info style) */ +.backup-browse-btn { background: #F0F9FF !important; border-color: #BAE6FD !important; color: #1d4ed8 !important; } + +/* File-restore: scroll-box with unlimited height override */ +.backup-scroll-box--full { max-height: none; } + +/* File-restore: filepath tag (monospace, truncated) */ +.backup-filepath-tag { font-family: monospace; font-size: 11px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; } + +/* File browser mobile: checkbox indicator */ +.backup-check-icon { color: #fff; font-size: 10px; } + +/* Restore modal: select-schedule label */ +.backup-select-label { display: block; margin-bottom: 8px; } + +/* Restore modal: schedule select width */ +.backup-schedule-select { width: 360px; } + +/* Job table: restore-type tag (orange accent, no border) */ +.backup-tag-xs--restore { background: #FFF7ED !important; color: #F97316 !important; } + +/* Mobile browse list: row with separator */ +.backup-browse-row { padding: 10px 0; border-bottom: 1px solid #F1F5F9; } + +/* ─── FileManager page BEM classes ───────────────────────────────────────── */ + +/* Breadcrumb clickable segments */ +.fm-crumb-link { cursor: pointer; } + +/* Main file list container (border + radius; padding is dynamic via inline style) */ +.fm-list-container { + background: #fff; + border: 1px solid #E5E7EB; + border-radius: 10px; +} + +/* File-type icon colors */ +.fm-icon--folder { color: #F59E0B; } +.fm-icon--file { color: #6B7280; } +.flex-shrink-0 { flex-shrink: 0; } + +/* Folder link in table name column */ +.fm-link--folder { color: #1d4ed8; } + +/* Selection info bar (blue tint, appears when rows are selected) */ +.fm-selection-bar { + padding: 8px 12px; + background: #F0F9FF; + border-radius: 8px; +} + +/* Clipboard banner (amber tint, shows cut/copy state) */ +.fm-clipboard-bar { + padding: 8px 12px; + background: #FFFBEB; + border-radius: 8px; +} + +/* Mobile card header bottom margin override (6 px instead of 8 px) */ +.fm-mb-6 { margin-bottom: 6px; } + +/* Mobile permission tag (monospace + no-shrink + left gap) */ +.fm-perm-tag { + font-family: monospace; + flex-shrink: 0; + margin-left: 8px; +} + +/* chmod modal: octal input (monospace, fixed narrow width) */ +.fm-chmod-input { + font-family: monospace; + width: 120px; +} + +/* Editor modal title row */ +.fm-editor-title { + display: flex; + align-items: center; + gap: 10px; +} + +/* Extension tag in editor modal title (compact, no margin) */ +.fm-editor-title-tag { + font-size: 10px; + margin: 0; +} + +/* Editor modal positioning + max-width */ +.fm-editor-modal { + top: 20px; + max-width: 1100px; +} + +/* Editor loading placeholder (centred, fixed height, muted) */ +.fm-editor-loading { + display: flex; + align-items: center; + justify-content: center; + height: 400px; + color: #64748B; +} + +/* CodeMirror wrapper inside editor modal */ +.fm-codemirror { + font-size: 13px; + border-radius: 0 0 8px 8px; + overflow: hidden; +} + +/* Upload dragger icon */ +.fm-upload-icon { + font-size: 32px; + color: #1677ff; +} + +/* DNS EmailSecurityTab */ +.dns-preview-box { + background: #F8FAFC; + border: 1px solid #E2E8F0; + border-radius: 8px; + padding: 10px 14px; + font-family: monospace; + font-size: 13px; + word-break: break-all; + color: #1E293B; +} + +/* ── Import page — static style replacements ──────────────────────────────── */ + +/* Spin: block + top margin (session list empty state) */ +.import-spin-mt { + display: block; + margin-top: 40px; +} + +/* Spin: block + centred with top/bottom margin (detail loading state) */ +.import-spin-center { + display: block; + margin: 60px auto; +} + +/* Session status tag in banner (rounded, bold, compact) */ +.import-session-status-tag { + border-radius: 6px; + font-weight: 600; + font-size: 12px; +} + +/* Banner chip row: flex + wrap + gap */ +.import-banner-chip-row { + display: flex; + flex-wrap: wrap; + gap: 10px; +} + +/* Progress card header row bottom margin */ +.mb-14 { margin-bottom: 14px; } + +/* Align items to text baseline */ +.align-baseline { align-items: baseline; } + +/* Gap-10 utility (complement to existing gap-8/gap-12) */ +.gap-10 { gap: 10px; } + +/* Error count icon (red, small) */ +.import-error-icon { + color: #EF4444; + font-size: 13px; +} + +/* Error count text (red, bold, tabular nums) */ +.import-error-count { + font-size: 13px; + font-weight: 600; + color: #EF4444; + font-feature-settings: "tnum"; +} + +/* Customer header left cell: flex-1 + no overflow collapse */ +.import-customer-header-left { + display: flex; + align-items: center; + flex: 1; + min-width: 0; + gap: 10px; +} + +/* Customer name/detail inner block */ +.import-customer-info { + min-width: 0; + flex: 1; +} + +/* Customer login text */ +.import-customer-login { + font-size: 14px; + font-weight: 700; + color: #0F172A; +} + +/* Item count tag inside customer header */ +.import-item-count-tag { + font-size: 11px; + margin: 0; + border-radius: 4px; +} + +/* Server/package select min-widths in customer header */ +.import-select-server { min-width: 130px; } +.import-select-package { min-width: 120px; } + +/* Item row source name text (static parts; fontFamily kept inline for dynamic) */ +.import-item-name { + flex: 1; + min-width: 120px; + font-weight: 600; + font-size: 13px; + color: #1E293B; +} + +.import-item-name--mono { + font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace; +} + +/* Item status tag (compact) */ +.import-item-status-tag { + border-radius: 5px; + font-size: 11px; + margin: 0; +} + +/* Error action icon (red, pointer) */ +.import-error-action { + color: #EF4444; + cursor: pointer; +} + +/* Traffic page */ +.traffic-stat-value { font-size: 24px; font-weight: 600; } +.traffic-table-wrap { overflow-x: auto; } +.traffic-table { width: 100%; border-collapse: collapse; } +.traffic-table thead tr { border-bottom: 1px solid #E5E7EB; } +.traffic-table__th--left { padding: 8px 12px; text-align: left; } +.traffic-table__th--right { padding: 8px 12px; text-align: right; } +.traffic-table__td { padding: 6px 12px; } +.traffic-table__td--right { padding: 6px 12px; text-align: right; } +.traffic-table tbody tr { border-bottom: 1px solid #F1F5F9; } + +/* ── SecurityAdvisor ──────────────────────────────────────────────────────── */ + +/* Score label block next to the progress circle */ +.secsadv-score-label { + text-align: left; +} + +/* Score title line: 16px displayed as block */ +.secsadv-score-title { + font-size: 16px; + display: block; +} + +/* AppArmor summary stat box — base (neutral border) */ +.secsadv-stat-box { + padding: 10px 14px; + border: 1px solid #E5E7EB; + border-radius: 8px; +} + +/* AppArmor stat box — warn variant (amber border) */ +.secsadv-stat-box--warn { + padding: 10px 14px; + border: 1px solid #F59E0B40; + border-radius: 8px; +} + +/* AppArmor stat box — teal/enforced variant */ +.secsadv-stat-box--teal { + padding: 10px 14px; + border: 1px solid #00D4AA40; + border-radius: 8px; +} + +/* Warn variant value color */ +.secsadv-stat-value--warn { + color: #D97706; +} + +/* Teal variant value color */ +.secsadv-stat-value--teal { + color: #00D4AA; +} + +/* Per-server AppArmor row */ +.secsadv-server-row { + padding: 8px 12px; + background: #F8FAFC; + border-radius: 6px; +} + +/* Check card header row — align items to top */ +.secsadv-check-header { + align-items: flex-start; +} + +/* Fix action link button — remove default Ant padding */ +.secsadv-link-btn { + padding: 0; +} + +/* ── Status icons (pass/warn/fail) ────────────────────────────────────── */ +.secsadv-icon { + font-size: 20px; +} + +.secsadv-icon--pass { color: #1677ff; } +.secsadv-icon--warn { color: #F59E0B; } +.secsadv-icon--fail { color: #EF4444; } + +/* ── Score label (responsive font size) ───────────────────────────────── */ +.secsadv-score-value { + font-size: 28px; + font-weight: 700; + color: var(--score-color, #1677ff); +} + +@media (max-width: 768px) { + .secsadv-score-value { + font-size: 22px; + } +} + +/* ── Check card with status accent border ─────────────────────────────── */ +.secsadv-check-card--pass { border-left: 3px solid #1677ff !important; height: 100%; } +.secsadv-check-card--warn { border-left: 3px solid #F59E0B !important; height: 100%; } +.secsadv-check-card--fail { border-left: 3px solid #EF4444 !important; height: 100%; } +/* ── Provider Selection ──────────────────────────────────────────────────── */ + +.import-provider-card { + background: #fff; + border: 2px solid #E2E8F0; + border-radius: 14px; + padding: 32px 24px; + text-align: center; + cursor: pointer; + transition: all 0.2s; +} + +.import-provider-card:hover { + border-color: #1677ff; + box-shadow: 0 4px 16px rgba(14, 165, 233, 0.15); + transform: translateY(-2px); +} + +.import-provider-card--disabled { + background: #F8FAFC; + border: 2px dashed #E2E8F0; + border-radius: 14px; + padding: 32px 24px; + text-align: center; + opacity: 0.6; +} + +.import-provider-logo { + width: 64px; + height: 64px; + border-radius: 16px; + margin: 0 auto 16px; + display: flex; + align-items: center; + justify-content: center; +} + +.import-provider-logo--plesk { + background: linear-gradient(135deg, #0080FF 0%, #0050CC 100%); + box-shadow: 0 4px 12px rgba(0, 80, 204, 0.2); +} + +.import-provider-logo--enhance { + background: linear-gradient(135deg, #475569 0%, #334155 100%); + box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2); +} + +.import-provider-logo--cpanel { + background: linear-gradient(135deg, #FF6C2C 0%, #D44A12 100%); + box-shadow: 0 4px 12px rgba(255, 108, 44, 0.2); +} + +.import-provider-logo--disabled { + background: #E2E8F0; +} + +.import-provider-logo span { + color: #fff; + font-size: 24px; + font-weight: 700; +} + +.import-provider-logo--disabled span { + color: #94A3B8; +} + +.import-provider-name { + font-size: 18px; + font-weight: 700; + color: #0F172A; + margin-bottom: 4px; +} + +.import-provider-name--disabled { + font-size: 18px; + font-weight: 700; + color: #94A3B8; + margin-bottom: 4px; +} + +.import-provider-desc { + font-size: 13px; + color: #64748B; + margin-bottom: 12px; +} + +.import-provider-desc--disabled { + font-size: 13px; + color: #94A3B8; + margin-bottom: 12px; +} + +/* ── Info/Mode Box ────────────────────────────────────────────────────────── */ + +.import-info-box { + background: #F0F9FF; + border: 1px solid #BAE6FD; + border-radius: 8px; + padding: 12px; + margin-bottom: 16px; + font-size: 12px; + color: #1d4ed8; +} + +.import-mode-box { + background: #F0F9FF; + border: 1px solid #BAE6FD; + border-radius: 10px; + padding: 16px; + margin-bottom: 16px; +} + +.import-mode-box__title { + font-size: 14px; + font-weight: 700; + color: #1d4ed8; + margin-bottom: 12px; +} + +.import-mode-item { + margin-bottom: 10px; +} + +.import-mode-item__title { + font-size: 13px; + font-weight: 600; + color: #1E293B; +} + +.import-mode-item__desc { + font-size: 12px; + color: #64748B; + margin-top: 2px; +} + +.import-mode-item__warn { + margin-top: 8px; +} + +/* ── Feature Overview Box ────────────────────────────────────────────────── */ + +.import-feature-box { + background: #F8FAFC; + border: 1px solid #E2E8F0; + border-radius: 12px; + padding: 20px; + margin-bottom: 20px; +} + +.import-feature-box__title { + font-size: 15px; + font-weight: 700; + color: #0F172A; + margin-bottom: 4px; +} + +.import-feature-box__subtitle { + font-size: 13px; + color: #64748B; + margin-bottom: 16px; +} + +.import-feature-item { + display: flex; + gap: 8px; + align-items: flex-start; +} + +.import-feature-item__icon { + font-size: 16px; + line-height: 20px; +} + +.import-feature-item__name { + font-size: 13px; + font-weight: 600; + color: #1E293B; +} + +.import-feature-item__desc { + font-size: 11px; + color: #94A3B8; +} + +/* ── Session Detail Banner ───────────────────────────────────────────────── */ + +.import-banner { + background: linear-gradient(135deg, #0B1426 0%, #1E3A5F 50%, #0D1829 100%); + border-radius: 10px 10px 0 0; + position: relative; + overflow: hidden; + padding: 16px; +} + +@media (min-width: 768px) { + .import-banner { padding: 24px 28px; } +} + +.import-banner__orb1 { + position: absolute; + top: -20px; + right: -20px; + width: 140px; + height: 140px; + border-radius: 50%; + background: rgba(0, 212, 170, 0.08); +} + +.import-banner__orb2 { + position: absolute; + bottom: -30px; + right: 80px; + width: 80px; + height: 80px; + border-radius: 50%; + background: rgba(14, 165, 233, 0.06); +} + +.import-banner__back-btn { + color: #94A3B8 !important; + padding: 4px 8px !important; + margin-bottom: 12px; + font-size: 13px !important; +} + +.import-banner__host { + color: #64748B; + font-size: 13px; +} + +.import-banner__type-tag { + background: rgba(255, 255, 255, 0.08) !important; + border: none !important; + color: #94A3B8 !important; + font-size: 11px !important; +} + +.import-banner__date { + color: #475569; + font-size: 12px; +} + +.import-banner__chip { + background: rgba(255, 255, 255, 0.06); + border-radius: 8px; + padding: 8px 14px; + backdrop-filter: blur(4px); + min-width: 80px; + text-align: center; +} + +.import-banner__chip-label { + font-size: 10px; + color: #64748B; + display: block; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.import-banner__chip-value { + font-size: 16px; + font-weight: 700; + color: #F8FAFC; + font-feature-settings: "tnum"; +} + +/* ── Content area ────────────────────────────────────────────────────────── */ + +.import-content-area { + padding: 16px; +} + +@media (min-width: 768px) { + .import-content-area { padding: 24px 28px; } +} + +/* ── Progress Card ───────────────────────────────────────────────────────── */ + +.import-progress-card { + background: #fff; + border: 1px solid #E2E8F0; + border-radius: 12px; + margin-bottom: 24px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); + padding: 16px; +} + +/* Import log drawer */ +.import-log-view { + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + font-size: 12.5px; + line-height: 1.5; +} +.import-log-line { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 4px 6px; + border-bottom: 1px solid #F1F5F9; + word-break: break-word; +} +.import-log-line--warn { background: #FFFBEB; } +.import-log-line--error { background: #FEF2F2; } +.import-log-time { + color: #94A3B8; + white-space: nowrap; + flex-shrink: 0; +} +.import-log-level { + flex-shrink: 0; + margin: 0; + font-size: 10px; + line-height: 18px; +} +.import-log-msg { color: #1E293B; flex: 1; } + +@media (min-width: 768px) { + .import-progress-card { padding: 20px 24px; } +} + +.import-progress-pct { + font-size: 36px; + font-weight: 800; + color: #0F172A; + font-feature-settings: "tnum"; + line-height: 1; +} + +.import-progress-label { + font-size: 13px; + color: #64748B; +} + +.import-progress-count { + font-size: 13px; + color: #64748B; + font-feature-settings: "tnum"; +} + +.import-progress-errors { + display: flex; + align-items: center; + gap: 4px; +} + +.import-progress-bar-track { + height: 8px; + border-radius: 4px; + background: #F1F5F9; + overflow: hidden; +} + +.import-progress-bar-fill { + height: 100%; + border-radius: 4px; + transition: width 0.6s ease; + width: var(--fill-width, 0%); + background: linear-gradient(90deg, #1677ff 0%, #1677ff 100%); +} + +.import-progress-bar-fill--error { + background: linear-gradient(90deg, #EF4444 0%, #F87171 100%); +} + +/* ── Customer Cards ──────────────────────────────────────────────────────── */ + +.import-customer-card { + background: #fff; + border: 1px solid #E2E8F0; + border-radius: 12px; + overflow: hidden; + transition: box-shadow 0.2s, border-color 0.2s; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); +} + +.import-customer-card--expanded { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06); + border-color: #CBD5E1; +} + +.import-customer-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + flex-wrap: wrap; + cursor: pointer; + padding: 12px 14px; +} + +@media (min-width: 768px) { + .import-customer-header { padding: 16px 20px; } +} + +.import-customer-header--expanded { + border-bottom: 1px solid #F1F5F9; +} + +.import-customer-avatar { + width: 36px; + height: 36px; + border-radius: 10px; + flex-shrink: 0; + background: linear-gradient(135deg, #475569 0%, #334155 100%); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 16px; + box-shadow: 0 2px 6px rgba(109, 40, 217, 0.25); +} + +.import-customer-status-dot { + width: 7px; + height: 7px; + border-radius: 50%; + flex-shrink: 0; + background: var(--dot-bg, #94A3B8); + box-shadow: var(--dot-shadow, none); +} + +.import-customer-mobile-selectors { + padding: 10px 14px; + background: #F8FAFC; + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.import-migrate-btn { + background: linear-gradient(135deg, #1677ff 0%, #1677ff 100%) !important; + border: none !important; + border-radius: 6px !important; + font-weight: 600 !important; + font-size: 12px !important; + box-shadow: 0 2px 6px rgba(0, 212, 170, 0.3) !important; +} + +.import-chevron { + font-size: 11px; + color: #94A3B8; + transition: transform 0.2s; +} + +.import-chevron--open { transform: rotate(180deg); } + +/* ── Item Rows ───────────────────────────────────────────────────────────── */ + +.import-item-row { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + flex-wrap: wrap; + background: #fff; + border-bottom: 1px solid #F1F5F9; +} + +.import-item-row:nth-child(even) { background: #F8FAFC; } +.import-item-row:last-child { border-bottom: none; border-radius: 0 0 12px 12px; } + +.import-item-type-badge { + width: 24px; + height: 24px; + border-radius: 6px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-size: 12px; + background: var(--badge-gradient, linear-gradient(135deg, #64748B 0%, #475569 100%)); +} + +.import-item-type-label { + font-size: 12px; + color: #64748B; + min-width: 60px; +} + +.import-item-migrate-btn { + color: #1677ff !important; + font-size: 12px !important; +} + +.import-item-sync-btn { + color: #64748B !important; + font-size: 12px !important; +} + +/* ── Bottom Action Bar ───────────────────────────────────────────────────── */ + +.import-action-bar { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 12px; + padding-top: 20px; + border-top: 2px solid #E2E8F0; +} + +.import-action-bar__left { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.import-action-btn--primary { + background: linear-gradient(135deg, #1677ff 0%, #1677ff 100%) !important; + border: none !important; + border-radius: 8px !important; + font-weight: 700 !important; + font-size: 14px !important; + height: 42px !important; + box-shadow: 0 4px 12px rgba(0, 212, 170, 0.3) !important; +} + +.import-action-btn--secondary { + border-radius: 8px !important; + font-weight: 600 !important; + font-size: 14px !important; + height: 42px !important; +} + +.import-notifications-toggle { + display: flex; + align-items: center; + gap: 6px; + margin-left: 4px; +} + +/* Banner session title text (static parts; font-size kept inline for responsive) */ +.import-banner__title-text { + font-weight: 700; + color: #F8FAFC; + font-size: 18px; +} + +@media (min-width: 768px) { + .import-banner__title-text { font-size: 22px; } +} + +/* ── Utility: display inline-block ──────────────────────────────────────── */ +.d-inline-block { display: inline-block; } + +/* ── Sites page — migrated inline styles ────────────────────────────────── */ + +/* Fixed-width filter inputs in log modal */ +.sites-log-filter-input { width: 200px; } +.sites-log-status-select { width: 110px; } + +/* Alert with negative top margin (pulls up toward form item) */ +.sites-alert-mt-neg8 { margin-top: -8px; } + +/* Thumbnail in domain column */ +.sites-thumb-table { + width: 48px; + height: 27px; + border-radius: 4px; + border: 1px solid #E5E7EB; + flex-shrink: 0; + object-fit: cover; +} + +/* Thumbnail filling its container (mobile card) */ +.sites-thumb-fill { + width: 100%; + height: 100%; + object-fit: cover; +} + +/* Delete-confirm modal paragraphs */ +.sites-delete-text { + color: #374151; + font-size: 14px; + line-height: 1.7; +} + +.sites-delete-text--mt { + color: #374151; + font-size: 14px; + margin-top: 12px; +} + +/* Danger highlight box (delete confirmation) */ +.sites-danger-box { + padding: 12px 16px; + background: #FEF2F2; + border-radius: 8px; + border: 1px solid #FECACA; + margin-top: 8px; + margin-bottom: 12px; +} + +/* Domain code label inside danger box */ +.sites-danger-box__code { + font-weight: 600; + color: #DC2626; +} + +/* Info strip (slice metrics / unavailable state) */ +.sites-info-strip { + padding: 8px 12px; + background: #F8FAFC; + border: 1px solid #E5E7EB; + border-radius: 6px; +} + +/* Wider info strip (slice metrics active state) */ +.sites-info-strip--lg { + padding: 10px 14px; + background: #F8FAFC; + border: 1px solid #E5E7EB; + border-radius: 6px; +} + +/* Minimum-width column inside info strip */ +.sites-info-col-min200 { min-width: 200px; } + +/* ── Setup / Wizard ─────────────────────────────────────────────────────── */ + +.setup-page { + min-height: 100vh; + background: #F8FAFC; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; +} + +.setup-card { + background: #fff; + border: 1px solid #E5E7EB; + border-radius: var(--radius-lg); + padding: 32px; + width: 100%; + max-width: 580px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); +} + +.setup-header { + margin-bottom: 28px; +} + +.setup-brand-name { + font-size: 16px; +} + +.setup-hint { + font-size: 12px; + display: block; + margin-top: -8px; + margin-bottom: 12px; + color: #94A3B8; +} + +.setup-mode-card { + border-color: #1677ff !important; + border-width: 2px !important; + height: 100%; +} + +.setup-mode-icon { + font-size: 48px; + color: #1677ff; + margin-bottom: 12px; +} + +.setup-tos-box { + margin-bottom: 16px; + padding: 12px 16px; + background: #F8FAFC; + border: 1px solid #E5E7EB; + border-radius: var(--radius-md); +} + +.mb-28 { margin-bottom: 28px; } + +/* ── PrestaShop shared utilities ─────────────────────────────────────────── */ + +/* Badge with blue background (replaces style={{ backgroundColor: '#1677ff' }}) */ +.badge-blue .ant-badge-count { background-color: #1677ff; } + +/* Mini stat cell used in detail overview (replaces inline textAlign/border/padding) */ +.ps-mini-stat { + text-align: center; + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 12px 8px; +} + +/* Completed-install domain hint in success modal */ +.ps-install-done-domain { + color: #64748b; + font-size: 12px; + margin-top: 12px; +} + +/* Domain link in list-view table (replaces color:#1677ff + cursor:pointer) */ +.ps-domain-link { + color: #1677ff; + cursor: pointer; +} + +/* Shop name sub-line in list-view table */ +.ps-shop-name-cell { + font-size: 12px; + color: #64748B; +} + +/* Search bar in list header toolbar */ +.ps-search-input { + max-width: 340px; + flex: 1; +} + +/* Language selector in setup header */ +.setup-lang-select { width: 120px; } + +/* ── Subscriptions page ─────────────────────────────────────────────────── */ + +/* Activate button (green outline, replaces style={{ color/borderColor #10B981 }}) */ +.sub-btn--activate { + color: #10B981 !important; + border-color: #10B981 !important; +} + +/* Mobile card title wrapper (replaces style={{ minWidth: 0, flex: 1 }}) */ +.sub-mobile-card__title-wrap { + min-width: 0; + flex: 1; +} + +/* Push element to the far right in a flex row (replaces style={{ marginLeft: 'auto' }}) */ +.ml-auto { margin-left: auto; } + +/* Mobile card action row separator (replaces style={{ borderTop/paddingTop }}) */ +.sub-mobile-card__actions { + border-top: 1px solid #F1F5F9; + padding-top: 8px; +} + +/* Filter dropdown widths */ +.sub-filter--w140 { width: 140px; } +.sub-filter--w160 { width: 160px; } + +/* Hint text below domain/server fields in create modal */ +.sub-domain-hint { + display: block; + margin-top: -12px; + margin-bottom: 12px; +} + +/* Section heading inside detail drawer */ +.sub-section-title { + font-size: 14px; + margin-bottom: 16px; + display: block; +} + +/* Section heading with tighter bottom margin */ +.sub-section-title--tight { + font-size: 14px; + margin-bottom: 8px; + display: block; +} + +/* "Not yet provisioned" text in master-FTP section */ +.sub-ftp-not-provisioned { + display: block; + margin-bottom: 12px; +} + +/* ── Customers page ──────────────────────────────────────────────────────── */ + +/* Generated password code block in PasswordModal */ +.cust-password-code { + background: #F1F5F9; + padding: 2px 8px; + border-radius: 4px; +} + +/* "Copy before closing" warning line in PasswordModal */ +.cust-password-warning { + margin-top: 12px; + font-size: 12px; + color: #EF4444; +} + +/* Unsuspend button (green outline) — table + mobile card */ +.cust-btn-unsuspend { + color: #10B981 !important; + border-color: #10B981 !important; +} + +/* Impersonate button (warm-orange) in mobile card */ +.cust-btn-impersonate { + background: #FFF7ED !important; + border-color: #FB923C !important; + color: #C2410C !important; + font-weight: 600; +} + +/* Flex container that fills remaining width (mobile card info column) */ +.cust-card-inner { + min-width: 0; + flex: 1; +} + +/* Delete-progress step list (grid layout) */ +.cust-delete-steps { + display: grid; + gap: 8px; + margin-top: 12px; +} + +/* Step row */ +.cust-delete-step { + display: flex; + align-items: center; + gap: 12px; + padding: 10px 14px; + background: #F8FAFC; + border-radius: 8px; + border: 1px solid #E2E8F0; + transition: all 0.3s ease; +} + +.cust-delete-step--running { + background: #F0F9FF; + border-color: #BAE6FD; +} + +.cust-delete-step__label { + flex: 1; + font-weight: 500; + color: #94A3B8; +} + +.cust-delete-step__label--running { color: #0369A1; } +.cust-delete-step__label--done { color: #1E293B; } + +/* Step status icon cell */ +.cust-step-icon { + font-size: 18px; + width: 24px; + text-align: center; +} + +/* Running-spinner span inside step icon cell */ +.cust-step-running { + color: #0EA5E9; + animation: spin 1s linear infinite; + display: inline-block; +} + +/* Pending circle span inside step icon cell */ +.cust-step-pending { + color: #CBD5E1; +} + +/* Step count badge (shown when step.count > 0) */ +.cust-step-count { + font-weight: 700; + color: #1677ff; + font-size: 13px; +} + +/* Error message at the bottom of the delete-progress modal */ +.cust-delete-error { + color: #EF4444; + margin-top: 8px; + font-size: 13px; +} + +/* ── System page (sys-) ───────────────────────────────────────────────────── */ + +/* Jail config table row separator */ +.sys-jail-row { border-bottom: 1px solid #F1F5F9; } + +/* Jail name code element */ +.sys-jail-name { + font-size: 12px; + color: #1E293B; + font-weight: 600; +} + +/* Reboot confirm dialog warning icon */ +.sys-reboot-warn-icon { + color: #F59E0B; + font-size: 18px; + flex-shrink: 0; + margin-top: 2px; +} + +/* OPcache PHP version selector */ +.sys-opcache-version-select { width: 110px; } + +/* OPcache section bottom divider — tight top margin */ +.sys-opcache-divider { margin: 12px 0 0; } + +/* Scrollable wrapper for jail config table */ +.overflow-x-auto { overflow-x: auto; } + +/* Jail config */ +.sys-jail-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; +} + +/* Jail config row */ +.sys-jail-thead-row { border-bottom: 2px solid #E5E7EB; } + +/* Jail config label
(first column) */ +.sys-jail-th--label { + text-align: left; + padding: 8px 12px; + color: #64748B; + font-weight: 600; +} + +/* Jail config action (last column, spacer) */ +.sys-jail-th--actions { padding: 8px 12px; } + +/* ── Reseller Dashboard (rdash-) ─────────────────────────────────────────── */ + +/* Impersonate button — amber accent */ +.rdash-btn--impersonate { + color: #F59E0B !important; + border-color: #F59E0B !important; +} + +/* Unsuspend button — primary blue accent */ +.rdash-btn--unsuspend { + color: #1677ff !important; + border-color: #1677ff !important; +} + +/* Quota overview card — subtle border + rounded corners */ +.rdash-quota-card { + border: 1px solid #E2E8F0 !important; + border-radius: 10px !important; +} + +/* ── Onboarding page (onboard-) ──────────────────────────────────────────── */ + +/* Path-card icon — 28px, blue accent */ +.onboard-path-icon--blue { + font-size: 28px !important; + color: #1677ff !important; +} + +/* Path-card icon — 28px, purple accent */ +.onboard-path-icon--purple { + font-size: 28px !important; + color: #475569 !important; +} + +/* Path card — bordered, full height */ +.onboard-path-card { + border: 1px solid #E5E7EB !important; + border-radius: 10px !important; + height: 100%; +} + +/* Arrow indicator at the end of an incomplete check-item row */ +.onboard-check-arrow { + margin-left: auto; + color: #94A3B8; + font-size: 12px !important; +} + +/* Quota section title — zero out Ant Design's default top margin */ +.rdash-quota-title { margin: 0 0 12px !important; } + +/* Table header row — space-between flex with 10px gap */ +.rdash-table-header { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 16px; +} + +/* Search + action button row */ +.rdash-table-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +/* Search input — fixed width */ +.rdash-search-input { width: 220px; } +.res-search-input { width: 260px; } + +/* Mobile action buttons row — display:flex so flex-wrap + gap-4 take effect */ +.rdash-mobile-actions { display: flex; } + +/* ── Malware Scanner page (malware-) ─────────────────────────────────────── */ + +/* Mobile card wrapper */ +.malware-mobile-card { + padding: 12px; + background: #fff; + border: 1px solid #E5E7EB; + border-radius: 10px; + margin-bottom: 8px; +} + +/* Mobile card header row: domain left, status right */ +.malware-mobile-card__header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +/* Domain label inside mobile card */ +.malware-mobile-card__title { font-size: 14px; } + +/* Last-scan / files-scanned meta line */ +.malware-mobile-card__meta { + font-size: 12px; + color: #6B7280; + margin-bottom: 10px; +} + +/* Scan-metadata grid at top of Findings Drawer */ +.malware-meta-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 12px; + padding: 12px; + background: #F9FAFB; + border: 1px solid #E5E7EB; + border-radius: 8px; +} + +/* MetricTile label */ +.malware-metric__label { + font-size: 11px; + color: #6B7280; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* MetricTile value */ +.malware-metric__value { + font-size: 16px; + font-weight: 600; + color: var(--metric-value-color, #1E293B); +} + +/* Clickable filter tag */ +.malware-filter-tag { + cursor: pointer; +} + +.malware-filter-tag--empty { + opacity: 0.5; +} + +/* Tag-row beneath a section heading (severity / status breakdown) */ +.malware-tag-row { + margin-top: 6px; + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +/* Individual finding card in the drawer list */ +.malware-finding-card { + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 12px; +} + +/* Spacer above finding action buttons */ +.malware-finding-actions { margin-top: 6px; } + +/* mt-6 utility (completes the mt-* set) */ +.mt-6 { margin-top: 6px; } + +/* ── WAF page ─────────────────────────────────────────────────────────────── */ + +/* Expanded-row Descriptions background */ +.waf-descriptions-bg { background: #FAFBFC; } + +/* Filter dropdowns in log toolbar */ +.waf-select-site-filter { width: 200px; } +.waf-select-severity-filter { width: 160px; } + +/* Exclusions form max-width container */ +.waf-exclusions-form { max-width: 720px; } + +/* Paranoia-level select */ +.waf-select-paranoia { width: 240px; } + +/* Excluded-rule tag (clickable, custom padding) */ +.waf-rule-tag { + cursor: pointer; + font-size: 13px; + padding: 2px 8px; +} + +/* ── Utility additions ────────────────────────────────────────────────────── */ +.text-amber { color: #D97706; } +.w-260 { width: 260px; } + +/* Outline button in Ant Design primary blue (replaces style={{ color/borderColor }}) */ +.btn-blue-outline { color: #1677ff !important; border-color: #1677ff !important; } + +/* ── Domains page ─────────────────────────────────────────────────────────── */ + +/* DNS record card (DNSRecordRow outer wrapper) */ +.dom-dns-record { + border: 1px solid #E2E8F0; + border-radius: 8px; + padding: 12px 14px; + background: #F8FAFC; +} + +/* Value + copy-button row inside a DNS record card */ +.dom-dns-value-row { + display: flex; + align-items: flex-start; + margin-top: 6px; +} + +/* Monospace value box inside a DNS record card (color kept inline — dynamic) */ +.dom-dns-value-box { + flex: 1; + background: #F1F5F9; + border: 1px solid #E2E8F0; + border-radius: 6px; + padding: 6px 8px; + word-break: break-all; + min-height: 28px; +} + +/* ── RecoveryWizard ─────────────────────────────────────────────────────── */ + +/* Primary-blue button override (Ant Design default primary) */ +.recov-btn-primary { + background: #1677ff; + border-color: #1677ff; +} + +/* Snapshot selection radio item */ +.recov-snapshot-radio { + padding: 8px 12px; + border: 1px solid #E5E7EB; + border-radius: 8px; + width: 100%; +} + +/* Dark terminal log box */ +.recov-log-box { + margin-top: 16px; + background: #0F172A; + color: #E2E8F0; + padding: 12px; + border-radius: 8px; + font-family: monospace; + font-size: 12px; + max-height: 280px; + overflow: auto; +} + +/* ── ResellerBranding page ────────────────────────────────────────────────── */ + +/* Mini sidebar preview shell */ +.rbrand-preview-sidebar { + background: linear-gradient(180deg, #0B1426 0%, #101D33 50%, #0D1829 100%); + border-radius: 8px; + padding: 16px; + margin-bottom: 12px; +} + +/* Logo + title row inside the mini sidebar */ +.rbrand-preview-logo-row { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 16px; +} + +/* Actual logo img inside the logo row */ +.rbrand-preview-logo-img { + border-radius: 4px; + object-fit: contain; + background: #fff; +} + +/* Fallback letter-avatar when no logo is uploaded */ +.rbrand-preview-avatar { + width: 28px; + height: 28px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + color: #fff; + background: var(--preview-gradient, linear-gradient(135deg, #1677ff, #1677ff)); +} + +/* Panel title label in the sidebar preview */ +.rbrand-preview-title { + color: #E2E8F0; + font-size: 13px; + font-weight: 600; +} + +.rbrand-preview-menu-item { + display: flex; + align-items: center; + gap: 8px; + padding: 6px 10px; + border-radius: 6px; + font-size: 12px; + margin-bottom: 2px; + color: var(--item-color, #94A3B8); + background: var(--item-bg, transparent); + border-left: var(--item-border, 3px solid transparent); +} + +/* Preview header bar (white bar below the sidebar) */ +.rbrand-preview-header { + background: #fff; + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 8px 12px; + font-size: 13px; +} + +.rbrand-preview-header-avatar { + width: 24px; + height: 24px; + border-radius: 50%; + background: var(--preview-gradient, linear-gradient(135deg, #1677ff, #1677ff)); +} + +/* ── Dashboard utilities ────────────────────────────────────────────────── */ + +/* Extra-small tag text (replaces style={{ fontSize: 10 }}) */ +.tag-xs { font-size: 10px; } + +/* Padding-12 utility */ +.p-12 { padding: 12px; } + +/* Agent-update tag in ServerCard footer (replaces style={{ fontSize:10, cursor:'pointer', margin:0 }}) */ +.dash-tag--update { + font-size: 10px; + cursor: pointer; + margin: 0; +} + +/* ── Utility: cursor ────────────────────────────────────────────────────── */ +.cursor-pointer { cursor: pointer; } + +/* ── Utility: width ─────────────────────────────────────────────────────── */ +.w-150 { width: 150px; } +.w-160 { width: 160px; } +.w-180 { width: 180px; } +.w-240 { width: 240px; } + +/* ── AuditLog ───────────────────────────────────────────────────────────── */ + +/* Expanded row Descriptions background */ +.audit-descriptions--expanded { + background: #FAFBFC; +} + +/* Details value: small text with word-break for long JSON values */ +.audit-details__value { + font-size: 12px; + word-break: break-all; +} + +/* ── SSL page ───────────────────────────────────────────────────────────── */ + +/* System-SSL card: slightly tinted background to distinguish from main content */ +.ssl-system__card { + background: #F8FAFC; +} + +/* System-SSL section title: block-level so it stacks above the server list */ +.ssl-system__title { + display: block; + margin-bottom: 12px; +} + +/* ── GitDeploy extras ───────────────────────────────────────────────────────── */ + +/* Centred spinner block inside the deploy modal */ +.gitdeploy__deploy-loading { + width: 100%; + align-items: center; + padding: 32px; +} + +/* Webhook providers list (GitHub / GitLab / Gitea instructions) */ +.gitdeploy__providers-list { + font-size: 13px; + color: #475569; +} + +/* ── Additional utilities ───────────────────────────────────────────────────── */ + +.max-w-280 { max-width: 280px; } +.mt-20 { margin-top: 20px; } +.mt-48 { margin-top: 48px; } + +/* Individual server chip inside SystemSSLSection */ +.ssl-server-item { + padding: 10px 14px; + border: 1px solid #E5E7EB; + border-radius: 8px; + background: #fff; + min-width: 200px; +} + +/* Summary strip pill (the colored-dot + count card) */ +.ssl-summary__item { + background: #fff; + border: 1px solid #E5E7EB; + border-radius: 8px; + padding: 8px 16px; +} + +/* Mobile card action row: separated from content by a hairline rule */ +.ssl-mobile-actions { + border-top: 1px solid #F1F5F9; + padding-top: 8px; +} + +/* ── WebStats ─────────────────────────────────────────────── */ +.min-w-200 { min-width: 200px; } + +.wstats__report-frame { + width: 100%; + height: 600px; + border: none; +} + +.wstats__empty-icon { + font-size: 48px; +} + +/* ScanProgressModal */ +.scan-progress__body { + width: 100%; + align-items: center; +} + +.scan-progress__file-count { + margin-top: 4px; + font-size: 12px; + color: #6B7280; + text-align: center; +} + +.scan-progress__metrics { + display: flex; + gap: 24px; + justify-content: center; + flex-wrap: wrap; +} + +.scan-progress__metric-tile { + text-align: center; + min-width: 90px; +} + +.scan-progress__metric-label { + font-size: 11px; + color: #6B7280; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.scan-progress__metric-value { + font-size: 18px; + font-weight: 600; + color: var(--value-color, #1E293B); + margin-top: 2px; +} + +.scan-progress__metric-value--mono { + font-family: monospace; +} + +/* ── Databases ────────────────────────────────────────────── */ +.db__grant-tag { + font-size: 11px; + margin: 0; +} + +.db__grant-checkbox { + font-size: 13px; +} + +.db__search-input { + width: 280px; +} + +/* Extensions page utilities */ +.text-body-dark { color: #1E293B; } + +.ext-model-load-bar { + padding: 8px; + border-top: 1px solid #E5E7EB; +} + +.ext-card__config-note { + font-size: 12px; + color: #64748B; + margin: 6px 0 0; + font-style: italic; +} + +.ext-progress__message { + margin-top: 12px; + font-size: 13px; + color: #64748B; + min-height: 20px; +} + +/* ── Logs ─────────────────────────────────────────────────── */ +.w-140 { width: 140px; } +.w-220 { width: 220px; } + +.logs__badge--count { + background-color: #64748B !important; + font-size: 11px; +} + +.logs__live-text { + font-size: 12px; + color: #1677ff; +} + +/* ── MailConfig ─────────────────────────────────────────────────────────────── */ + +.mailcfg__section-card { + border: 1px solid #E5E7EB !important; + border-radius: 10px !important; + margin-bottom: 16px; +} + +.mailcfg__queue-panel { + background: #fff; + border: 1px solid #E5E7EB; + border-radius: 10px; + padding: 20px; +} + +.mailcfg__mono-bold { + font-family: monospace; + font-weight: 600; +} +.mailcfg__threshold-tag { + margin: 0; + line-height: 1; +} + +/* ── ResellerPlans (rplan-) ──────────────────────────────────────────────── */ + +/* Section divider — top of tab, no top margin */ +.rplan__divider--top.ant-divider { font-size: 12px; margin: 0 0 8px; } + +/* Section divider — between sections */ +.rplan__divider--mid.ant-divider { font-size: 12px; margin: 8px 0; } + +/* Section divider — top of policies tab, no top margin, larger bottom */ +.rplan__divider--top-lg.ant-divider { font-size: 12px; margin: 0 0 12px; } + +/* Section divider — between sections, larger bottom */ +.rplan__divider--mid-lg.ant-divider { font-size: 12px; margin: 8px 0 12px; } + +/* Text-align utility */ +.text-right { text-align: right; } + +/* ── AIAssistant ─────────────────────────────────────────────────────────────── */ + +.min-w-140 { min-width: 140px; } + +.ai-header__badge--provider { + background: #F0FDF4; + border: 1px solid #BBF7D0; + color: #166534; + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; +} + +.ai-header__badge--model { + background: #EFF6FF; + border: 1px solid #BFDBFE; + color: #1D4ED8; + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 500; + font-family: ui-monospace, Consolas, monospace; +} + +/* TLS Health — inline style migrations */ +.text-blue-700 { color: #1D4ED8; } +.max-w-260 { max-width: 260px; } +.tls-expanded-row { padding: 8px 16px; background: #F8FAFC; font-size: 13px; } + +/* Notifications — inline style migrations */ +.notif__smtp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; } +.notif__filter-status { width: 140px; min-width: 120px; } +.notif__filter-event { width: 200px; min-width: 150px; } +.notif__template-body { font-size: 12px; } +.notif__html-preview { border: 1px solid #E5E7EB; border-radius: 6px; padding: 16px; background: #fff; max-height: 500px; overflow-y: auto; } +.mobile-card__header--compact { margin-bottom: 6px; } + +/* Firewall — inline style migrations */ +.bg-white { background: #fff; } +.fw-handle-text { font-family: monospace; font-size: 12px; color: #64748B; } + +/* Cronjobs — inline style migrations */ +.cron-card__actions { border-top: 1px solid #F1F5F9; padding-top: 8px; } + +/* DNS — inline style migrations */ +.dns-table--dimmed { opacity: 0.7; } + +/* ProtectedDirs / Packages / FTP — inline style migrations */ +.word-break-all { word-break: break-all; } +.mb-6 { margin-bottom: 6px; } +.w-280 { width: 280px; } + +/* ─── ErrorBoundary ──────────────────────────────────────────────────────── */ +.offline-banner { + border-radius: 0 !important; + border-left: none !important; + border-right: none !important; + font-weight: 500; +} + +.error-boundary__stack { + margin-top: 24px; + padding: 16px; + background: #FEF2F2; + border: 1px solid #FECACA; + border-radius: 8px; + font-size: 12px; + color: #B91C1C; + overflow: auto; + white-space: pre-wrap; +} + +/* ─── Page lazy-load fallback ───────────────────────────────────────────── */ +.page-loader { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: #F8FAFC; +} + +.page-loader::after { + content: ''; + width: 32px; + height: 32px; + border: 3px solid #E2E8F0; + border-top-color: #00D4AA; + border-radius: 50%; + animation: page-loader-spin 0.6s linear infinite; +} + +@keyframes page-loader-spin { + to { transform: rotate(360deg); } +} + +/* ── Form layout utilities ───────────────────────────────────────────────────── */ +.form-grid-2col { + display: grid; + grid-template-columns: 1fr; + gap: 0 16px; +} +@media (min-width: 768px) { + .form-grid-2col { grid-template-columns: 1fr 1fr; } +} + +.card-content-responsive { + padding: 20px; +} +@media (max-width: 767px) { + .card-content-responsive { padding: 12px; } +} + +/* ── Width utilities ─────────────────────────────────────────────────────────── */ +.w-full-mobile { + width: 320px; +} +@media (max-width: 767px) { + .w-full-mobile { width: 100%; } +} + +.mt-16-mobile { margin-top: 0; } +@media (max-width: 991px) { + .mt-16-mobile { margin-top: 16px; } +} + +/* Bulk SSL modal */ +.ssl-bulk-hint { color: #64748B; font-size: 13px; margin-bottom: 12px; } +.ssl-bulk-counts { margin: 12px 0; } +.ssl-bulk-progress-text { color: #64748B; font-variant-numeric: tabular-nums; } +.ssl-bulk-list { max-height: 320px; overflow-y: auto; margin-top: 8px; } +.ssl-bulk-list .ant-list-item { display: flex; gap: 8px; align-items: baseline; } +.ssl-bulk-domain { font-weight: 600; } +.ssl-bulk-msg { color: #94A3B8; font-size: 12px; margin-left: auto; text-align: right; } + +/* Uptime monitor — status strip (status-page look) */ +.uptime-strip { display: flex; gap: 2px; align-items: stretch; height: 34px; margin: 4px 0 14px; border-radius: 6px; overflow: hidden; } +.uptime-strip__seg { flex: 1 1 0; min-width: 2px; border-radius: 2px; transition: opacity .15s; } +.uptime-strip__seg:hover { opacity: .7; } diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..baf539e --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/netcell-it/deklarix + +go 1.26.6 diff --git a/packaging/DEBIAN/control.tmpl b/packaging/DEBIAN/control.tmpl new file mode 100644 index 0000000..de79ceb --- /dev/null +++ b/packaging/DEBIAN/control.tmpl @@ -0,0 +1,7 @@ +Package: deklarix +Version: VERSION +Architecture: ARCH +Maintainer: NetCell IT +Description: Deklarix — deklarix.de / deklarix.com + Deklarix Software Package. +Depends: diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..680882c --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Deklarix — Build-Skript (amd64 + arm64) +# +# Verwendung: +# ./scripts/build.sh [version] # Beide Architekturen +# ./scripts/build.sh [version] amd64 # Nur amd64 +# ./scripts/build.sh [version] arm64 # Nur arm64 +# +# Ausgabe: dist/deklarix__ +# +# Release-Prozess: +# 1. VERSION erhöhen (Semantic Versioning: MAJOR.MINOR.PATCH) +# 2. ./scripts/build.sh +# 3. ./scripts/test.sh +# 4. git tag v && git push origin v + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +VERSION="${1:-0.0.1}" +ARCH_FILTER="${2:-both}" + +GRN='\033[0;32m'; BLD='\033[1m'; NC='\033[0m' +log() { echo -e "${GRN}[build]${NC} $*"; } + +export PATH=$PATH:/usr/local/go/bin + +command -v go >/dev/null || { echo "Go nicht gefunden — PATH: $PATH"; exit 1; } + +mkdir -p "$REPO_DIR/dist" + +log "Version: $VERSION | Go: $(go version)" + +ARCHS=() +case "$ARCH_FILTER" in + amd64) ARCHS=("amd64") ;; + arm64) ARCHS=("arm64") ;; + *) ARCHS=("amd64" "arm64") ;; +esac + +for ARCH in "${ARCHS[@]}"; do + OUT="$REPO_DIR/dist/deklarix_${VERSION}_${ARCH}" + log "Baue $ARCH → $OUT" + GOARCH=$ARCH GOOS=linux go build \ + -ldflags="-X main.Version=${VERSION} -s -w" \ + -o "$OUT" \ + ./cmd/server/ + log "$ARCH fertig: $(du -sh "$OUT" | cut -f1)" +done + +log "Build abgeschlossen ✓" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..7bfa341 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# Deklarix — Release-Skript +# +# Verwendung: ./scripts/release.sh +# Beispiel: ./scripts/release.sh 1.2.0 +# +# Was passiert: +# 1. Tests laufen +# 2. Build für amd64 + arm64 +# 3. git tag v wird gesetzt +# 4. git push origin main + git push origin v +# +# Voraussetzungen: +# - Sauberer Git-Status (keine uncommitted changes) +# - Alle Tests grün + +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +VERSION="${1:-}" + +if [[ -z "$VERSION" ]]; then + echo "Verwendung: $0 (z.B. 1.0.0)" + exit 1 +fi + +GRN='\033[0;32m'; BLD='\033[1m'; NC='\033[0m' +log() { echo -e "${GRN}[release]${NC} $*"; } + +cd "$REPO_DIR" + +# Git-Status prüfen +if [[ -n "$(git status --porcelain)" ]]; then + echo "FEHLER: Uncommitted changes vorhanden — bitte erst committen" + git status --short + exit 1 +fi + +log "Release v${VERSION} wird vorbereitet..." + +# Tests +log "Tests laufen..." +bash "$REPO_DIR/scripts/test.sh" + +# Build +log "Build für alle Architekturen..." +bash "$REPO_DIR/scripts/build.sh" "$VERSION" + +# Git Tag +log "Tag v${VERSION} setzen..." +git tag -a "v${VERSION}" -m "Release v${VERSION}" + +# Push +log "Push main + Tag..." +git push origin main +git push origin "v${VERSION}" + +log "" +log "Release v${VERSION} fertig ✓" +log "Binaries: dist/deklarix_${VERSION}_amd64 + _arm64" diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..c26690d --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Deklarix — Test-Skript +# +# Führt alle Go-Tests aus + Vet + Build-Check. +# Wird auch im CI/vor jedem Release ausgeführt. + +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +export PATH=$PATH:/usr/local/go/bin + +GRN='\033[0;32m'; RED='\033[0;31m'; NC='\033[0m' +log() { echo -e "${GRN}[test]${NC} $*"; } +fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; } + +cd "$REPO_DIR" + +log "go vet ..." +go vet ./... || fail "go vet fehlgeschlagen" + +log "go test ./... " +go test -race -count=1 ./... || fail "Tests fehlgeschlagen" + +log "Build-Check (amd64) ..." +GOARCH=amd64 GOOS=linux go build -o /dev/null ./cmd/server/ || fail "Build fehlgeschlagen" + +log "Alle Tests bestanden ✓"