Phase 1 — Authentication, Persistence & Settings Page
This commit is contained in:
@@ -1,174 +1,340 @@
|
||||
# TASK: Bug fixes from v0.6.2 code scan
|
||||
# TASK: Phase 1 — Authentication, Persistence & Settings Page
|
||||
|
||||
## Context
|
||||
**Version target:** 0.7.0
|
||||
**Repo:** `deploy-felhom-compose` (controller)
|
||||
|
||||
Comprehensive code scan of felhom-controller v0.6.2 found 4 minor bugs across templates, shell scripts, and Go code. None are critical, but all should be fixed for correctness.
|
||||
## Overview
|
||||
|
||||
**Current state:** Controller v0.6.2 running on demo-felhom.eu.
|
||||
Three workstreams in this phase:
|
||||
1. Implement login/logout authentication for the controller dashboard
|
||||
2. Persist DB validation results across container restarts
|
||||
3. Add "Beállítások" (Settings) page with config display and password change
|
||||
|
||||
All changes in this task are in the **deploy-felhom-compose** repo only.
|
||||
All user-editable state is stored in a single `settings.json` file at:
|
||||
`/opt/docker/felhom-controller/data/settings.json`
|
||||
|
||||
This path is already bind-mounted into the container (the `data/` dir holds `restic-password` etc.).
|
||||
The controller.yaml config remains the source of truth for operator-provisioned values;
|
||||
`settings.json` holds customer-modifiable overrides.
|
||||
|
||||
---
|
||||
|
||||
## Bug 1: Missing `require_arg` for `--hdd-path` in docker-setup.sh
|
||||
## 1. settings.json — Shared Persistence Layer
|
||||
|
||||
**File:** `scripts/docker-setup.sh`
|
||||
|
||||
**Problem:** The `--hdd-path` flag parsing doesn't use `require_arg` validation like all other flags do. Under `set -u`, if `--hdd-path` is the last argument and has no value, `$2` is unbound and the script crashes with a cryptic bash error instead of a friendly message.
|
||||
|
||||
**Current code** (in the argument parsing `while` loop):
|
||||
|
||||
```bash
|
||||
--hdd-path) HDD_PATH="$2"; shift 2 ;;
|
||||
### 1.1 Create `internal/settings/settings.go`
|
||||
```
|
||||
File: controller/internal/settings/settings.go
|
||||
```
|
||||
|
||||
**Fix:** Add `require_arg` call, matching the pattern used by all other flags:
|
||||
|
||||
```bash
|
||||
--hdd-path)
|
||||
require_arg "$1" "${2:-}"
|
||||
HDD_PATH="$2"; shift 2 ;;
|
||||
```
|
||||
|
||||
**Verification:** Search for other flags in the same `while` loop — they all use `require_arg`. Confirm `require_arg` is defined earlier in the script (it is).
|
||||
|
||||
---
|
||||
|
||||
## Bug 2: Implicit `event` variable in `stackAction()` (layout.html)
|
||||
|
||||
**File:** `controller/internal/web/templates/layout.html`
|
||||
|
||||
**Problem:** The `stackAction` JavaScript function references `event.currentTarget` to get the clicked button, but `event` is never passed as a parameter. It relies on the implicit global `window.event` object, which is non-standard and deprecated. Works in Chrome/Firefox today but is not guaranteed.
|
||||
|
||||
**Current code:**
|
||||
|
||||
```javascript
|
||||
async function stackAction(name, action) {
|
||||
const btn = event.currentTarget;
|
||||
```
|
||||
|
||||
**Fix — Step 1:** Change the function signature to accept `event`:
|
||||
|
||||
```javascript
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
```
|
||||
|
||||
**Fix — Step 2:** Update ALL `onclick` call sites in the same file that call `stackAction` to pass `event` as the first argument. Search for `stackAction(` in the template — each call looks like:
|
||||
|
||||
```html
|
||||
onclick="stackAction('{{.Name}}', 'start')"
|
||||
```
|
||||
|
||||
Change each to:
|
||||
|
||||
```html
|
||||
onclick="stackAction(event, '{{.Name}}', 'start')"
|
||||
```
|
||||
|
||||
There are multiple call sites (start, stop, restart buttons in the stacks section). Update **all** of them.
|
||||
|
||||
**Verification:** Search the entire file for `stackAction(` — every call site must pass `event` as the first argument. No other functions in the codebase call `stackAction`.
|
||||
|
||||
---
|
||||
|
||||
## Bug 3: Missing separator in page title (layout.html)
|
||||
|
||||
**File:** `controller/internal/web/templates/layout.html`
|
||||
|
||||
**Problem:** The `<title>` tag concatenates `.Title` and "Felhom.eu" with no separator, rendering as e.g. `"VezérlőpultFelhom.eu"` instead of `"Vezérlőpult — Felhom.eu"`.
|
||||
|
||||
**Current code:**
|
||||
|
||||
```html
|
||||
<title>{{.Title}}Felhom.eu</title>
|
||||
```
|
||||
|
||||
**Fix:**
|
||||
|
||||
```html
|
||||
<title>{{.Title}} — Felhom.eu</title>
|
||||
```
|
||||
|
||||
Uses em dash (U+2014) with spaces on both sides. This is a single-character change in the template.
|
||||
|
||||
**Edge case:** If `.Title` is empty, the title becomes ` — Felhom.eu` (leading space + dash). Check if any handler sets an empty `.Title`. If so, consider using a conditional:
|
||||
|
||||
```html
|
||||
<title>{{if .Title}}{{.Title}} — {{end}}Felhom.eu</title>
|
||||
```
|
||||
|
||||
Check all handlers that call `render()` or `renderTemplate()` — if every handler always sets a non-empty `.Title`, the simple fix (without conditional) is fine.
|
||||
|
||||
---
|
||||
|
||||
## Bug 4: `nextPruneLabel` edge case on Sunday before 4am (funcmap.go)
|
||||
|
||||
**File:** `controller/internal/web/funcmap.go`
|
||||
|
||||
**Problem:** The `nextPruneLabel` function calculates when the next weekly prune (Sunday 4:00) will occur. On Sunday before 4am, `daysUntilSunday` computes to 0, but the function returns the date in `"2006-01-02"` format instead of `"ma"` (Hungarian for "today"). Every other "today" scenario in the codebase uses the `"ma"` label.
|
||||
|
||||
**Current code:**
|
||||
|
||||
Define the settings struct and load/save logic:
|
||||
```go
|
||||
daysUntilSunday := (7 - int(now.Weekday())) % 7
|
||||
if daysUntilSunday == 0 && now.Hour() >= 4 {
|
||||
daysUntilSunday = 7
|
||||
type Settings struct {
|
||||
mu sync.RWMutex `json:"-"`
|
||||
|
||||
// Auth
|
||||
PasswordHash string `json:"password_hash,omitempty"` // bcrypt hash, overrides controller.yaml
|
||||
|
||||
// Notification preferences (Phase 2 — define struct now, leave empty)
|
||||
Notifications *NotificationPrefs `json:"notifications,omitempty"`
|
||||
|
||||
// Cached state
|
||||
DBValidations map[string]DBValidationCache `json:"db_validations,omitempty"`
|
||||
}
|
||||
|
||||
type NotificationPrefs struct {
|
||||
// placeholder for Phase 2
|
||||
}
|
||||
|
||||
type DBValidationCache struct {
|
||||
ValidatedAt string `json:"validated_at"` // RFC3339
|
||||
TableCount int `json:"table_count"`
|
||||
HasHeader bool `json:"has_header"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
next := time.Date(now.Year(), now.Month(), now.Day()+daysUntilSunday, 4, 0, 0, 0, now.Location())
|
||||
return next.Format("2006-01-02")
|
||||
```
|
||||
|
||||
The logic breakdown:
|
||||
- Sunday, hour >= 4: `daysUntilSunday` = 0 → set to 7 (next week). Correct.
|
||||
- Sunday, hour < 4: `daysUntilSunday` = 0 → stays 0, returns today's date as `"2006-01-02"`. Should return `"ma"`.
|
||||
- Any other day: `daysUntilSunday` > 0 → returns future date. Correct.
|
||||
**Behavior:**
|
||||
- `Load(path string) (*Settings, error)` — reads file, returns empty Settings{} if file doesn't exist (not an error)
|
||||
- `Save() error` — atomic write (write to `.tmp`, rename). Path stored internally from Load.
|
||||
- Use `sync.RWMutex` for concurrent access (backup goroutine writes validations, web reads them)
|
||||
- Log on every save: `[DEBUG] Settings saved to <path>`
|
||||
|
||||
**Fix:**
|
||||
**File location:** The controller's docker-compose.yml already mounts `./data:/opt/docker/felhom-controller/data`.
|
||||
The settings file path should be passed to the Settings manager on init.
|
||||
|
||||
```go
|
||||
daysUntilSunday := (7 - int(now.Weekday())) % 7
|
||||
if daysUntilSunday == 0 {
|
||||
if now.Hour() >= 4 {
|
||||
daysUntilSunday = 7 // Already ran today, next week
|
||||
} else {
|
||||
return "ma" // Today (Sunday), hasn't run yet
|
||||
}
|
||||
}
|
||||
next := time.Date(now.Year(), now.Month(), now.Day()+daysUntilSunday, 4, 0, 0, 0, now.Location())
|
||||
return next.Format("2006-01-02")
|
||||
```
|
||||
|
||||
**Verification:** Mentally walk through all cases:
|
||||
- Monday–Saturday: `daysUntilSunday` is 1–6, returns future date ✓
|
||||
- Sunday 03:00: returns `"ma"` ✓
|
||||
- Sunday 04:00: `daysUntilSunday` = 7, returns next Sunday ✓
|
||||
- Sunday 23:00: `daysUntilSunday` = 7, returns next Sunday ✓
|
||||
**Testing:** On startup, if file doesn't exist, log `[INFO] No settings.json found, using defaults`. Do NOT create the file until something actually needs saving.
|
||||
|
||||
---
|
||||
|
||||
## Build & Deploy
|
||||
## 2. Authentication (Login / Logout)
|
||||
|
||||
After all fixes, commit and deploy as v0.6.3:
|
||||
### 2.1 Password resolution
|
||||
|
||||
```bash
|
||||
# 1. Commit
|
||||
cd /e/git/deploy-felhom-compose
|
||||
git add -A && git commit -m "fix: require_arg for --hdd-path, explicit event in stackAction, title separator, nextPruneLabel Sunday edge case" && git push
|
||||
The effective password hash is determined with this priority:
|
||||
1. `settings.json` → `password_hash` (customer changed it)
|
||||
2. `controller.yaml` → `web.password_hash` (operator provisioned)
|
||||
3. Empty string → no auth required (current testing behavior)
|
||||
|
||||
# 2. Build (only needed for bugs 2-4 which affect the controller binary/templates)
|
||||
ssh kisfenyo@192.168.0.180 "cd ~/build/felhom-controller && ./build.sh 0.6.3 --push"
|
||||
|
||||
# 3. Deploy to demo node
|
||||
ssh kisfenyo@192.168.0.162 "docker pull gitea.dooplex.hu/admin/felhom-controller:0.6.3 && cd /opt/docker && docker compose up -d"
|
||||
|
||||
# 4. Verify
|
||||
ssh kisfenyo@192.168.0.162 "docker logs felhom-controller --tail 5"
|
||||
On startup, log which source is active:
|
||||
```
|
||||
[INFO] Auth: using password from settings.json
|
||||
[INFO] Auth: using password from controller.yaml
|
||||
[INFO] Auth: no password configured — dashboard is open
|
||||
```
|
||||
|
||||
## Post-deploy checklist
|
||||
### 2.2 Session management
|
||||
|
||||
- [ ] Page title shows separator: "Vezérlőpult — Felhom.eu" (check browser tab)
|
||||
- [ ] Stack start/stop/restart buttons still work (Bug 2 didn't break onclick handlers)
|
||||
- [ ] `docker-setup.sh --hdd-path` without value shows friendly error (test locally)
|
||||
- [ ] Backup page shows "ma" on Sunday before 4am (only testable at that time, or adjust system clock)
|
||||
Use a **signed session cookie** approach (not just `"authenticated"` string):
|
||||
- Generate a random 32-byte session secret on startup (store in memory only, not persisted — restarts invalidate sessions, which is fine)
|
||||
- Cookie name: `felhom_session`
|
||||
- Cookie value: `<expiry_unix>.<hmac_sha256_hex>` — HMAC of expiry timestamp with session secret
|
||||
- `HttpOnly: true`, `SameSite: Strict`, `Secure: false` (local network, self-signed certs)
|
||||
- `MaxAge: 7 days` (configurable later)
|
||||
- `Path: /`
|
||||
|
||||
### 2.3 Routes
|
||||
|
||||
Add to `internal/web/server.go` routing:
|
||||
|
||||
| Method | Path | Auth? | Handler |
|
||||
|--------|-------------|-------|--------------------|
|
||||
| GET | `/login` | No | Show login form |
|
||||
| POST | `/login` | No | Validate + set cookie + redirect to `/` |
|
||||
| GET/POST | `/logout` | No | Clear cookie + redirect to `/login` |
|
||||
|
||||
**Auth middleware** (wrap all routes except `/login`, `/logout`, `/style.css`, `/assets/*`, `/api/*`):
|
||||
- Check `felhom_session` cookie → validate HMAC + check expiry
|
||||
- If invalid/missing → redirect to `/login` (for browser) or return 401 (for API)
|
||||
- If no password configured → pass through (no auth)
|
||||
|
||||
### 2.4 Login page template
|
||||
```
|
||||
File: controller/internal/web/templates/login.html
|
||||
```
|
||||
|
||||
- Standalone page (no sidebar), same dark theme as rest of dashboard
|
||||
- Felhom logo centered at top
|
||||
- Single password field + "Bejelentkezés" button
|
||||
- On error: "Hibás jelszó" message (red, below form)
|
||||
- Customer name shown below logo (from config)
|
||||
- No username field — single-user system
|
||||
|
||||
### 2.5 Logout
|
||||
|
||||
- `GET /logout` or `POST /logout` → set cookie MaxAge=-1 → redirect to `/login`
|
||||
- Add logout link to sidebar bottom (near version display):
|
||||
```
|
||||
<version> | Kijelentkezés
|
||||
```
|
||||
Only show "Kijelentkezés" if auth is enabled.
|
||||
|
||||
### 2.6 Edge cases
|
||||
|
||||
- If password_hash is empty in both sources → no auth, no login page, no logout link
|
||||
- If user is on a page and session expires → next request redirects to `/login`, after login redirect back to original page (use `?next=/backups` query param)
|
||||
- Cookie cleared on logout must work even if server secret rotated (clear by MaxAge=-1)
|
||||
|
||||
---
|
||||
|
||||
## 3. DB Validation Persistence
|
||||
|
||||
### 3.1 Problem
|
||||
|
||||
After container restart, the backup page "Érvényesítés" column shows "–" for all databases until the next backup cycle runs validation. The v0.6.2 cross-check helps once RefreshCache runs, but the initial load after restart still shows no data.
|
||||
|
||||
### 3.2 Solution
|
||||
|
||||
**On validation completion** (`internal/backup/dbdump.go` → `ValidateDump`):
|
||||
- After successful validation, save result to `settings.json` via settings package:
|
||||
```go
|
||||
settings.SetDBValidation("immich-postgres.sql", DBValidationCache{
|
||||
ValidatedAt: time.Now().Format(time.RFC3339),
|
||||
TableCount: 60,
|
||||
HasHeader: true,
|
||||
})
|
||||
// SetDBValidation acquires write lock, updates map, calls Save()
|
||||
```
|
||||
|
||||
**On startup / RefreshCache** (`internal/backup/backup.go`):
|
||||
- Load cached validations from `settings.json`
|
||||
- For each dump file that exists on disk AND has a cached validation:
|
||||
- Use the cached validation data
|
||||
- Set `DumpValidation.Valid = true/false` based on cached result
|
||||
- Set `DumpValidation.Message` to include cached info: e.g., `"60 tábla (utolsó: 08:04)"`
|
||||
- The next actual validation run overwrites the cache with fresh data
|
||||
|
||||
**Important:** The cache is keyed by dump filename (e.g., `immich-postgres.sql`). If a dump file no longer exists, its cached validation is ignored (stale data cleanup).
|
||||
|
||||
### 3.3 Template update
|
||||
|
||||
No template changes needed — the existing 4-branch guard from v0.6.2 already handles showing validation status correctly. The only difference is now the data will be populated from cache on startup instead of being zero-valued.
|
||||
|
||||
---
|
||||
|
||||
## 4. "Beállítások" (Settings) Page
|
||||
|
||||
### 4.1 Sidebar menu item
|
||||
|
||||
Add "Beállítások" as the last menu item, visually separated from the main navigation — placed at the bottom of the sidebar, just above the version/logout section. Use a gear/cog icon (⚙ or SVG).
|
||||
|
||||
Sidebar order:
|
||||
```
|
||||
Vezérlőpult
|
||||
Alkalmazások
|
||||
Biztonsági mentés
|
||||
Rendszermonitor
|
||||
─── (spacer / flex-grow) ───
|
||||
⚙ Beállítások ← new, pinned to bottom
|
||||
v0.7.0 | Kijelentkezés
|
||||
```
|
||||
|
||||
### 4.2 Route
|
||||
|
||||
| Method | Path | Auth? | Handler |
|
||||
|--------|-------------|-------|---------------------|
|
||||
| GET | `/settings` | Yes | Show settings page |
|
||||
| POST | `/settings/password` | Yes | Change password |
|
||||
|
||||
### 4.3 Settings page layout
|
||||
|
||||
The page has two sections:
|
||||
|
||||
#### Section A: "Rendszer konfiguráció" (System Configuration) — Read-only
|
||||
|
||||
Display key values from `controller.yaml` in a clean info-grid. These are read-only — the customer can see what's configured but can't change it here.
|
||||
|
||||
| Label (Hungarian) | Source in controller.yaml | Display format |
|
||||
|--------------------------|----------------------------------|----------------|
|
||||
| Ügyfél azonosító | `customer.id` | `demo-felhom` |
|
||||
| Ügyfél neve | `customer.name` | `Demo Ügyfél` |
|
||||
| Domain | `customer.domain` | `demo-felhom.eu` |
|
||||
| Alkalmazás sablon forrás | `git.repo_url` | URL (truncated) |
|
||||
| Sablon szinkronizálás | `git.sync_interval` | `15m` |
|
||||
| Biztonsági mentés | `backup.enabled` | ✅ Aktív / ❌ Inaktív |
|
||||
| Mentés ütemezés | `backup.db_dump_schedule` + `backup.restic_schedule` | `02:30 / 03:00` |
|
||||
| Monitoring | `monitoring.enabled` | ✅ Aktív / ❌ Inaktív |
|
||||
| Healthchecks URL | `monitoring.healthchecks_base` | URL or "–" |
|
||||
| Hub jelentés | `hub.enabled` (if exists) | ✅ Aktív / ❌ Inaktív / "–" |
|
||||
| Controller verzió | built-in Version constant | `0.7.0` |
|
||||
|
||||
Use green checkmark / red X styling for boolean states, consistent with the backup page.
|
||||
|
||||
#### Section B: "Jelszó módosítás" (Change Password) — Editable
|
||||
|
||||
Only shown if auth is enabled (password_hash is set). If no auth, show an info message:
|
||||
"A jelszavas védelem nincs beállítva. Kérd az üzemeltetőt a beállításhoz."
|
||||
|
||||
Form fields:
|
||||
- Jelenlegi jelszó (current password) — required
|
||||
- Új jelszó (new password) — required, min 8 chars
|
||||
- Új jelszó megerősítése (confirm new password) — required, must match
|
||||
|
||||
**POST `/settings/password` handler:**
|
||||
1. Validate current password against effective hash (bcrypt compare)
|
||||
2. Validate new password: min 8 chars, both fields match
|
||||
3. Generate bcrypt hash (cost 10) for new password
|
||||
4. Save to `settings.json` → `password_hash`
|
||||
5. Invalidate current session (regenerate session secret so all cookies become invalid)
|
||||
6. Redirect to `/login` with success flash message: "Jelszó sikeresen módosítva. Kérjük, jelentkezzen be az új jelszóval."
|
||||
|
||||
**Error handling:**
|
||||
- Wrong current password → "Hibás jelenlegi jelszó" (stay on page)
|
||||
- Passwords don't match → "A két jelszó nem egyezik"
|
||||
- Too short → "A jelszónak legalább 8 karakter hosszúnak kell lennie"
|
||||
- Show errors inline, don't clear form
|
||||
|
||||
### 4.4 Template
|
||||
```
|
||||
File: controller/internal/web/templates/settings.html
|
||||
```
|
||||
|
||||
Follow the same card-based layout as the backup page. Two cards:
|
||||
1. "Rendszer konfiguráció" — info-grid with labels + values
|
||||
2. "Jelszó módosítás" — form card
|
||||
|
||||
---
|
||||
|
||||
## 5. Implementation Order
|
||||
|
||||
Follow this sequence to keep each step testable:
|
||||
|
||||
### Step 1: settings.json package
|
||||
- Create `internal/settings/settings.go` with Settings struct, Load/Save
|
||||
- Add settings instance to the controller's main app struct
|
||||
- Load on startup, log result
|
||||
- **Test:** Start controller, check logs for settings load message
|
||||
|
||||
### Step 2: Authentication
|
||||
- Add session secret generation on startup
|
||||
- Add auth middleware
|
||||
- Add login.html template
|
||||
- Add login/logout handlers
|
||||
- Add logout link to sidebar
|
||||
- Wire up routes in server.go
|
||||
- Set `web.password_hash` in controller.yaml on demo to test
|
||||
- **Test:** Navigate to dashboard → redirected to /login → enter password → dashboard loads → /logout → back to /login
|
||||
|
||||
### Step 3: DB validation persistence
|
||||
- After ValidateDump completes, save results to settings.json
|
||||
- On RefreshCache, load cached validations for initial display
|
||||
- **Test:** Deploy apps with DBs → trigger backup → check Érvényesítés column shows data → restart container → check column still shows data
|
||||
|
||||
### Step 4: Settings page
|
||||
- Add settings.html template
|
||||
- Add settings route + handler
|
||||
- Add sidebar menu item with bottom-pinning
|
||||
- Implement password change POST handler
|
||||
- **Test:** Open /settings → see config values → change password → re-login with new password
|
||||
|
||||
### Step 5: Cleanup & version bump
|
||||
- Update CONTEXT.md
|
||||
- Bump version to 0.7.0
|
||||
- Build + deploy + verify on demo-felhom.eu
|
||||
|
||||
---
|
||||
|
||||
## 6. Files to Create / Modify
|
||||
|
||||
### New files:
|
||||
- `controller/internal/settings/settings.go` — Settings persistence
|
||||
- `controller/internal/web/templates/login.html` — Login page
|
||||
- `controller/internal/web/templates/settings.html` — Settings page
|
||||
|
||||
### Modified files:
|
||||
- `controller/internal/web/server.go` — Add auth middleware, new routes (/login, /logout, /settings, /settings/password), session management, settings page handler
|
||||
- `controller/internal/web/templates/` sidebar partial or base template — Add "Beállítások" menu item at bottom, logout link
|
||||
- `controller/internal/backup/backup.go` — Load cached validations in RefreshCache
|
||||
- `controller/internal/backup/dbdump.go` — Save validation results to settings.json
|
||||
- `controller/internal/config/config.go` — Possibly add data_dir path helper
|
||||
- `controller/cmd/controller/main.go` — Initialize settings, pass to web server and backup manager
|
||||
|
||||
---
|
||||
|
||||
## 7. Design Decisions & Notes
|
||||
|
||||
### Why settings.json (not SQLite)?
|
||||
- Single file, human-debuggable, easy to backup/restore
|
||||
- Tiny data volume (password hash + a few validation entries)
|
||||
- No query needs — just load/save whole struct
|
||||
- Customers or operators can inspect/reset it easily
|
||||
|
||||
### Why not modify controller.yaml for password changes?
|
||||
- controller.yaml is operator-provisioned config, risky to programmatically rewrite YAML
|
||||
- settings.json is a clean override layer: operator sets initial password in yaml, customer changes it in json
|
||||
- If settings.json is deleted, system falls back to controller.yaml password (recovery path)
|
||||
|
||||
### Session secret is ephemeral (memory only)
|
||||
- Container restart = all sessions invalidated = users must re-login
|
||||
- This is acceptable and actually desirable for security
|
||||
- No need to persist session state
|
||||
|
||||
### Notifications (Phase 2 prep)
|
||||
- The Settings struct includes a `Notifications` field placeholder
|
||||
- Phase 2 will add: email relay via k3s (similar to contact-mailer pattern), notification preferences UI
|
||||
- The relay approach: customer controller sends HTTP POST to a central notification API on k3s, which handles Resend delivery. Avoids storing Resend API keys on customer hardware.
|
||||
- This keeps secrets centralized and customer nodes lightweight.
|
||||
|
||||
### Settings page scope — what goes where
|
||||
- "Beállítások" = actual settings (things the user can configure or needs to know about their setup)
|
||||
- "Rendszermonitor" = live system state (hostname, uptime, CPU, RAM, disk, Docker containers)
|
||||
- No overlap — config is static, monitoring is dynamic
|
||||
Reference in New Issue
Block a user