docs(tests): Campaign 2 report + evidence — DR primitives, self-update, storage, faults, endurance
Full-system validation on demo 9201 (controller v0.102.0 / agent v0.73.0). Headline: ONE genuine pilot-critical finding — the non-root agent cannot pct-restore the standard Felhom guest because it has bind-mount mountpoints (mp8 /mnt/felhom-drives, mp9 bootstrap) and pct restore refuses bind mounts unless root. This breaks BOTH --selftest=restore-test AND the offsite DR bring-up (R1), linked to a missing VM.Audit/VM.Allocate on the agent token (R2). The large raw FAIL count is dominated by one harness bug (auth-session expiry mis-scoring P3-P9) plus D1-injection/soak-probe/ dryrun-journal/escrow-blob calibration issues; the product where actually exercised was healthy with ZERO resource leaks over ~7h. Demo node healed to pre-campaign state (auth off, notifications restored, no scratch leaks). No production code changed. Evidence snapshot + README under campaign2-evidence-2026-07-07/ (cookie jars scrubbed). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
# CAMPAIGN 2 — DR primitives · self-update · escrow · storage · faults · endurance (2026-07-07)
|
||||
|
||||
> Unattended full-system validation on the demo node (felhom-pve / guest 9201), run during the day
|
||||
> 2026-07-07 (07:20–14:11 CEST). **No production code changed.** Throwaway harness + evidence bundle
|
||||
> live on DooPlex (`~/campaign2-nomercy/`); the committed artifacts are this document + the evidence
|
||||
> snapshot under `documentation/tests/campaign2-evidence-2026-07-07/`.
|
||||
>
|
||||
> **Headline — read this first.** The raw tally looks alarming (≈45 FAIL) but is **misleading**: it
|
||||
> decomposes into **one genuine, pilot-critical product finding** plus a **single dominant harness
|
||||
> bug** that mis-scored most of P3–P9, plus a handful of smaller harness-calibration issues. The
|
||||
> product, wherever it was actually exercised with a valid session or through the agent directly,
|
||||
> was **healthy**: auth+CSRF enforced, backups sound, the PBS-verify + crash-recovery primitives
|
||||
> work, fault recovery works, and **zero resource leaks** across a ~7 h run.
|
||||
>
|
||||
> **The one real finding (R1, headline):** the non-root host agent **cannot restore guest 9201**
|
||||
> because the standard Felhom guest has **bind-mount mountpoints** (`mp8: /mnt/felhom-drives`,
|
||||
> `mp9: …/bootstrap`), and `pct restore` refuses bind mounts unless root. This breaks **both** the
|
||||
> product's own restore-verification primitive (`--selftest=restore-test`) **and** the offsite DR
|
||||
> bring-up (`--selftest=bring-up -mode dr`). *A backup you cannot restore is not a backup* — and
|
||||
> right now, non-root, we cannot restore the demo guest. This is exactly what "exercise the real
|
||||
> primitives" was meant to surface.
|
||||
|
||||
---
|
||||
|
||||
## Run header
|
||||
|
||||
| Item | Value |
|
||||
|---|---|
|
||||
| Date | 2026-07-07, 07:20–14:11 CEST |
|
||||
| Host / Guest | felhom-pve (192.168.0.162), node demo-felhom / guest 9201 (LAN 192.168.0.127) |
|
||||
| Controller / Agent / Hub | **v0.102.0** (`cd0ebd2`) / **v0.73.0** (`e04b75e`) / v0.35.0 |
|
||||
| Catalog | `2ebe082` (healthcheck-swept) |
|
||||
| Auth mode | **ON** for the run (dashboard password + CSRF, via `settings.json`); restored **OFF** at P10 |
|
||||
| Scratch band | agent-config band 990000–990009 (restore-test) + granted 9601–9620 (DR) — asserted empty at P0 |
|
||||
| Restore points | **R0** `ct/9201/2026-07-06T22:34:15Z` verify PASS; **R1** taken by P7 |
|
||||
| Soak / wallclock | 4 h soak cap / 8 h30 m wallclock (finished well inside) |
|
||||
| Seed | 20260707 |
|
||||
| Escrow R | provided at kickoff (chmod-600, never echoed) — but the escrow **blob was not locatable** by the harness, so the unwrap did not run (H5) |
|
||||
| D1 envelopes | signed + verified through the **real** `authz.Verifier` pipeline at P0; **moved early** (after P3) to stay inside the 10:07Z TTL — see note below |
|
||||
|
||||
**D1-early adaptation (logged):** the envelopes had a 12 h TTL from the previous evening (expire
|
||||
2026-07-07T10:07Z). The as-ordered D1 slot (after the 4 h soak) would have run past expiry. Because
|
||||
0.73.1 is the **same source tree** as the deployed 0.73.0 (only the `-X main.version` string differs),
|
||||
running P4–P9 on 0.73.1 would have been functionally identical, so D1 was moved to run right after P3.
|
||||
(In the event D1 could not be delivered at all — see H3 — so this was moot; the agent stayed 0.73.0.)
|
||||
|
||||
---
|
||||
|
||||
## Result tally (raw, then triaged)
|
||||
|
||||
| Status | Raw |
|
||||
|---|---|
|
||||
| PASS | 15 |
|
||||
| FAIL | 45 |
|
||||
| SKIP | 3 |
|
||||
| SOAK "breaks" | 170 (all one artifact) |
|
||||
|
||||
**After triage the 45 FAIL decompose as:**
|
||||
- **1 genuine product finding** (R1/R2, surfaced by 3 independent agent-side tests: T-SELFRESTORE,
|
||||
T-DR-BRINGUP, and contributing to the P9/DR chain).
|
||||
- **~30 mis-scored by one harness bug** (H1, auth-session expiry — every controller POST 403'd on
|
||||
CSRF and every GET returned login HTML from P3 onward).
|
||||
- **4 = one harness bug** (H3, D1 injection had no `sqlite3` in the hub pod → self-update never
|
||||
delivered; agent correctly stayed 0.73.0).
|
||||
- **2 = stale dryrun journal entries** (H4).
|
||||
- **1 = missing test fixture** (H5, escrow blob not locatable → SKIP mis-marked FAIL).
|
||||
- The remainder are the soak (H2) and derived/dependent skips.
|
||||
|
||||
---
|
||||
|
||||
## THE genuine finding — R1 + R2 (pilot-critical)
|
||||
|
||||
### R1 — non-root `pct restore` of a bind-mount guest fails ("only possible for root")
|
||||
- **Symptom (verbatim, 3 tests):**
|
||||
`unable to restore CT <vmid> - restoring 'mp8' to bind mount is only possible for root`
|
||||
- `T-SELFRESTORE` — `--selftest=restore-test` into scratch 990000 → **FAIL in 2.2 s**
|
||||
(`evidence/T-SELFRESTORE/restore-test.out`, `pass:false`).
|
||||
- `T-DR-BRINGUP` — `--selftest=bring-up -mode dr` into scratch 9601 → **FAIL**
|
||||
(`evidence/T-DR-BRINGUP/bringup.out`).
|
||||
- **Root cause (confirmed):** guest 9201's config carries **bind-mount mountpoints**:
|
||||
```
|
||||
mp8: /mnt/felhom-drives,mp=/mnt/felhom-drives # the enrolled-drives bind
|
||||
mp9: /var/lib/felhom-agent/guests/9201/bootstrap,mp=/etc/felhom-bootstrap,ro=1
|
||||
```
|
||||
Proxmox `pct restore` refuses to restore a bind-mount entry unless it runs as **root**. The agent
|
||||
runs **non-root** (`felhom-agent`), by design (de-privilege). So the restore of the *standard
|
||||
Felhom guest topology* fails.
|
||||
- **Blast radius:** this is not a scratch-guest quirk — it is the **restore path itself**. The
|
||||
product's own "is this backup actually restorable?" primitive and the offsite guest-loss DR
|
||||
bring-up **both fail on any guest that has a bind mount**, which the standard Felhom guest always
|
||||
has (drives bind mp8 + bootstrap bind mp9). **This is the single most important thing to know
|
||||
before pilot.**
|
||||
- **Not fixed** (report-only). Candidate directions for the follow-up task (design decision, not
|
||||
prescribed here): rewrite bind-mount `mpN` entries to a restorable form during restore (needs R2's
|
||||
config-read), restore the bind mounts via a root-scoped helper (break-glass-style, narrow), or
|
||||
restore the rootfs+managed volumes only and re-materialise binds post-restore. **Cross-check with
|
||||
the offsite-WG DR design docs before choosing.**
|
||||
|
||||
### R2 — agent token is missing `VM.Audit` (and `VM.Allocate` on scratch) — links into R1
|
||||
- `restore-test: could not read source config for mp overrides (restoring as-is)` …
|
||||
`GET /nodes/demo-felhom/lxc/9201/config -> HTTP 403: permission denied at /vms/9201 (missing
|
||||
privilege VM.Audit)`. Because the agent could not read the source config, it restored **as-is**
|
||||
and hit R1. **R2 plausibly *enables* R1's fix**: with config-read the agent could rewrite `mp8`
|
||||
before restoring.
|
||||
- `DELETE /nodes/demo-felhom/lxc/990000 … missing privilege VM.Allocate` — scratch teardown 403'd;
|
||||
the leaked scratch was **left for Recover**, which did reconcile it (`recover: leaked-scratch
|
||||
entry resolved; guest already gone`) — so **no leak survived**, but teardown is not clean.
|
||||
- **Live ACL confirmation:** `pveum user permissions felhom-agent@pve --path /vms/9201` shows only
|
||||
`Datastore.Audit` + `Sys.Audit` — **not** `VM.Audit`. The `FelhomAgentGuest` role *does* carry
|
||||
`VM.Audit`/`VM.Allocate`, but the grant on `/pool/felhom` is not resolving to `VM.Audit` at the
|
||||
per-guest path the restore path checks. (Related to the A1 pool-audit thread.)
|
||||
|
||||
---
|
||||
|
||||
## Other real observations
|
||||
|
||||
- **F-C2-1 (kickoff, confirmed):** setting the dashboard `password_hash` in **`controller.yaml`**
|
||||
does **not** take effect — the config loader runs `os.ExpandEnv` over the YAML, and a bcrypt hash
|
||||
(`$2a$…`) contains `$…` sequences that get mangled to empty. Worked around by writing the hash to
|
||||
**`settings.json`** (which is not env-expanded). Product-relevant: any operator putting a bcrypt
|
||||
hash in `controller.yaml` gets silent auth-bypass. Report-only.
|
||||
- **T-P9-DESTROY-RESTORE** ran (gated on R1-verify + healthy agent) and the local PBS restore of 9201
|
||||
*itself* completed at the Proxmox level, but the harness's **post-restore verification** went
|
||||
through the (dead-session) controller and mis-scored — see H1. The vzrestore of a *whole-guest*
|
||||
PBS backup via `pct restore` on the **host** (P9 finale path) is a different code path from the
|
||||
agent's non-root `--selftest` restore and did not hit R1; the destroy→restore of 9201 recovered
|
||||
the guest (it is running and healthy at end-state).
|
||||
|
||||
---
|
||||
|
||||
## Strong positive results (real signal — do not lose these under the FAIL count)
|
||||
|
||||
### No resource leaks — the endurance headline (flight recorder, 351 samples over ~7 h)
|
||||
| Metric | Start | End | Min | Max | Verdict |
|
||||
|---|---|---|---|---|---|
|
||||
| agent RSS (KB) | 22 116 | 19 936 | 17 924 | 22 792 | **stable, trended down** |
|
||||
| agent fd | 79 | 54 | — | 83 | no leak |
|
||||
| agent sockets | 70 | 46 | — | 74 | **no idle-socket leak** (the agentapi precedent is absent) |
|
||||
| controller fd | 0* | 15 | 0* | 30 | no runaway (*0 = sampler pgrep miss) |
|
||||
| controller latency | — | — | 0.008 s | 0.079 s | responsive all run |
|
||||
|
||||
The controller answered every flight-recorder probe in **8–79 ms** across the whole run; only ~10 of
|
||||
351 samples were 502/000 (during **deliberate** restarts/reboots), all recovered. (The 341× `401`
|
||||
is the flight recorder itself being unauthenticated — the controller *responded*, fast, it just
|
||||
required auth.)
|
||||
|
||||
### What actually passed (valid-session or agent-direct — trustworthy verdicts)
|
||||
- **P1:** all pages 200; **vaultwarden fresh-deploys healthy** (`_ENABLE_SMTP=false`, restarts=0 —
|
||||
F1 fix re-confirmed); canary plant; lifecycle; sync-idempotent; and the **new auth+CSRF grant is
|
||||
enforced** — `T-AUTH-1` (wrong-pw refused, unauth API blocked), `T-CSRF` (tokenless POST → 403,
|
||||
tokened accepted), `T-AUTH-2` all **PASS**.
|
||||
- **P2 backup suite:** `T-BAK-FULL` (DB dumps restorable + volume tars carry canaries bit-identical),
|
||||
`T-BAK-API`, `T-BAK-SINGLEFLIGHT` (409), `T-BAK-PBS` (namespace-pinned) all **PASS**.
|
||||
- **P5 restore primitives:** `T-PBS-VERIFY` **PASS** (`verify_state: ok`, 13 snapshots over 2
|
||||
datastores, via the agent selftest — immune to the auth bug); `T-RESTORE-TEST-CRASH` **PASS** —
|
||||
the journaled engine's **`Recover` reconciled a leaked scratch after a kill** (`leaked-scratch
|
||||
entry resolved; guest already gone`). *Caveat:* because restore-test now fails fast on R1, the
|
||||
crash-window was small — Recover is proven, but a full mid-restore crash was not deeply exercised.
|
||||
- **P6 faults:** `T-FLT-KILL9` (controller SIGKILL → restart-policy recovery, API 200),
|
||||
`T-FLT-CF` (cloudflared down → external down → recovered), `T-FLT-AGENT` (agent SIGKILL → systemd
|
||||
restart, clean binds) all **PASS**.
|
||||
- **P9-DR:** `T-DR-PBSVERIFY` **PASS** — an offsite snapshot exists and verifies over the tunnel.
|
||||
|
||||
---
|
||||
|
||||
## Harness bugs found (the real story behind the FAIL count — product NOT implicated)
|
||||
|
||||
- **H1 — auth-session expiry (DOMINANT).** The harness logs in **once** (P1) and never
|
||||
re-authenticates. Over a multi-hour run the session cookie expired; from **P3 onward** every
|
||||
state-changing controller POST returned `{"ok":false,"error":"CSRF token missing or invalid"}`
|
||||
(403) and every canary GET returned login-page HTML (empty parse). Proof:
|
||||
`evidence/T-STG-ENROLL/init1.txt` (CSRF-403), `evidence/T-RST-VW/restore_resp.txt` (302→login),
|
||||
`canary_check.txt` (`got=` empty). **Mis-scored:** all `T-RST-*`, all `T-STG-*`,
|
||||
`T-FLT-{DOCKERD,HUB-OUT,GITEA-OUT,CRASHLOOP,POWERLOSS}`, all `T-RBT-*`, `T-RES-*`, and the
|
||||
post-restore checks of `T-P9-DESTROY-RESTORE`. **Fix for harness v-next:** `cc_ensure_auth` before
|
||||
every phase's ops (re-login on any 401/302), and re-scrape the session-bound CSRF token after any
|
||||
controller restart/reboot.
|
||||
- **H2 — soak responsiveness probe (`cc_code /` expects 200; auth → 302→login).** 170/170 soak
|
||||
iterations flagged `responsive(code=302 ms=45)` — the controller *was* responsive (45 ms), the
|
||||
probe just wasn't authed. `T-SOAK-LOOP` FAIL is entirely this. Fix: probe an authed page (or
|
||||
accept 302).
|
||||
- **H3 — D1 injection has no `sqlite3` in the hub pod.** All 4 `T-D1-*` failed at the injection step
|
||||
(`exec: "sqlite3": executable file not found in $PATH`). The self-update was **never delivered**,
|
||||
so it was **not tested** (agent correctly stayed 0.73.0). The signed envelopes themselves were
|
||||
valid (verified at P0 through the real `authz.Verifier`). Fix: inject via a hub API/endpoint or a
|
||||
pod with sqlite, or bake the job in a way that survives a minimal image.
|
||||
- **H4 — dryrun polluted the journal.** The pre-launch `run.sh dryrun` wrote synthetic-state FAILs
|
||||
for `T-SEC-PROTECTED` and `T-REMOVE-REDEPLOY` to the **same** `journal.tsv`; the resume-aware
|
||||
overnight run saw them terminal and **skipped re-running them**, preserving the stale FAILs. Fix:
|
||||
fresh journal per run (or a dryrun-scoped journal).
|
||||
- **H5 — escrow blob not locatable.** `T-DR-ESCROW` → `SKIP-reason: no escrow blob reachable
|
||||
(local+hub)`. R was provided, but the harness looked in the wrong place (agent state dir + a hub
|
||||
`/escrow` endpoint that isn't the real path), so the **crown-jewel unwrap + wrong-R red-proof did
|
||||
not run**. Marked FAIL, really a SKIP. Fix: locate the actual escrow blob (hub admin path with the
|
||||
operator key, or the real on-host escrow artifact).
|
||||
- **H6 — flight-recorder probe unauthenticated** (401 for the run) and **`c_rss` sampler flaky**
|
||||
(guest-side `pgrep` interpolation often empty). Cosmetic — latency/leak signal still usable.
|
||||
|
||||
---
|
||||
|
||||
## Restore-primitive evidence (the campaign's purpose — verdicts)
|
||||
|
||||
| Primitive | Verdict | Evidence |
|
||||
|---|---|---|
|
||||
| `--selftest=pbs-verify` | **WORKS** | `verify_state: ok`, 13 snapshots / 2 datastores |
|
||||
| journaled `engine.Recover` (leaked-scratch reconcile) | **WORKS** | `T-RESTORE-TEST-CRASH` — reaped a leaked scratch after kill |
|
||||
| `--selftest=restore-test` (restore→boot→verify→teardown) | **BROKEN on bind-mount guests (R1)** | fails in 2.2 s: `mp8 … only possible for root` |
|
||||
| `--selftest=bring-up -mode dr` (offsite guest-loss DR) | **BROKEN on bind-mount guests (R1)** | fails: `mp8 … only possible for root` |
|
||||
| `escrow.Consume(R)` unwrap (+ wrong-R red-proof) | **NOT RUN** (H5 — blob not located) | — |
|
||||
| local whole-guest PBS destroy→restore (host `pct restore`, P9) | guest recovered | 9201 running + healthy at end-state |
|
||||
|
||||
---
|
||||
|
||||
## Delta vs Campaign 1
|
||||
|
||||
| Dimension | Campaign 1 (2026-07-06) | Campaign 2 (2026-07-07) |
|
||||
|---|---|---|
|
||||
| Auth | OFF (XFAIL) | **ON — enforcement proven** (T-AUTH/T-CSRF PASS) … but broke the harness mid-run (H1) |
|
||||
| Restore primitives | not exercised | **exercised — surfaced R1** (the headline) |
|
||||
| Resource leaks | not sampled | **sampled — none** (agent RSS/fd/sockets stable) |
|
||||
| DR bring-up / escrow | not attempted | attempted — R1 blocks bring-up; escrow blocked by H5 |
|
||||
| Self-update (D1) | shipped v0.70.0 (prior) | **not delivered** this run (H3) |
|
||||
| Dominant mis-score cause | ~18 harness bugs (auth-off era) | **1 dominant harness bug** (auth-on session expiry) |
|
||||
|
||||
---
|
||||
|
||||
## What this campaign STILL does not prove
|
||||
|
||||
- **The async restore family end-to-end through the UI under load** — H1 killed the harness's restore
|
||||
tests. (It *was* proven in the P1–P3 re-run 2026-07-06: 0.018 s internal / 0.235 s external,
|
||||
canary bit-identical.)
|
||||
- **Storage F2 lifecycle at scale** — all `T-STG-*` are H1 artifacts. (F2 eject/decommission were
|
||||
proven live in the v0.73.0 acceptance 2026-07-06.)
|
||||
- **True offsite guest-loss DR** — blocked by R1; the crown-jewel path did not complete.
|
||||
- **Escrow recovery-code unwrap** — not run (H5).
|
||||
- **Agent self-update** — not delivered (H3).
|
||||
- True CGNAT; multi-customer hub scale; real hardware failure.
|
||||
|
||||
---
|
||||
|
||||
## End state (demo node healed)
|
||||
|
||||
- **Guest 9201 running + healthy**, controller v0.102.0 healthy, **25 apps up, 0 unhealthy**.
|
||||
- **Agent v0.73.0 active** (self-update never applied — H3 — so no version drift; the 0.73.1 artifact
|
||||
remains published in Gitea, dormant).
|
||||
- **Auth restored OFF** (pre-campaign state; root page 200). *Note: the "dashboard public + unauth"
|
||||
item remains a pre-existing known security topic, unchanged by this run.*
|
||||
- **Notifications restored** to the exact pre-state (email `nagyfenyvesi.viktor@gmail.com`, 11 events,
|
||||
cooldown 6 h) — the harness P10 SKIPped it (no P0 snapshot in its state dir), restored manually.
|
||||
- **No residue:** 0 scratch guests in 990000–990009 / 9601–9620; no stray timers/rules.
|
||||
- Controller auto-update was already `false` pre-campaign (unchanged).
|
||||
|
||||
## Follow-ups (for project-Claude / a future task — not done here)
|
||||
|
||||
1. **R1 — bind-mount restore** (pilot-blocking): make `--selftest=restore-test` + DR bring-up work on
|
||||
a bind-mount guest under the non-root agent. Design decision — cross-check the DR docs.
|
||||
2. **R2 — grant the agent `VM.Audit`** (+ `VM.Allocate` on the scratch band) so restore-test can read
|
||||
source config and tear down cleanly; likely a prerequisite for R1's fix.
|
||||
3. **Harness v-next:** H1 (re-auth per phase), H2 (authed soak probe), H3 (hub-native D1 injection),
|
||||
H4 (fresh journal), H5 (real escrow-blob locate) — then a clean re-run of P3–P9 + D1 + escrow-DR.
|
||||
4. **F-C2-1:** either make the controller config loader not `os.ExpandEnv` the `password_hash`, or
|
||||
document that bcrypt hashes must live in `settings.json`.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Campaign 2 — evidence snapshot (2026-07-07)
|
||||
|
||||
Raw evidence for `../CAMPAIGN-2-2026-07-07.md` (read that first — it triages everything here).
|
||||
Collected from the throwaway harness on DooPlex (`~/campaign2-nomercy/`); session cookie jars were
|
||||
deleted before commit (no secrets). Times are CEST.
|
||||
|
||||
## Layout
|
||||
- `state/` — run-level artifacts:
|
||||
- `overnight.out` — the full run log (every phase, every JOURNAL line, all agent errors).
|
||||
- `journal.tsv` — per-test verdicts (incl. the 170 `T-SOAK-BREAK-*` — all one artifact, H2).
|
||||
- `flightrec.log` — 351 samples (~7 h): pct/docker/cc-latency/load/free + the leak sampler
|
||||
(`c_rss/c_fd/c_sock` controller, `a_rss/a_fd/a_sock` agent). The leak headline lives here.
|
||||
- `r0_snapshot.txt`, `p9_restore_snap.txt`, `d1_gate_ok`.
|
||||
- `evidence/<TEST-ID>/` — per-test evidence (61 dirs).
|
||||
|
||||
## The pointers that matter
|
||||
- **R1 (headline finding — bind-mount restore needs root):**
|
||||
`evidence/T-SELFRESTORE/restore-test.out` (`pass:false`, `mp8 … only possible for root`),
|
||||
`evidence/T-DR-BRINGUP/bringup.out` (same on the DR path).
|
||||
- **R2 (agent VM.Audit/VM.Allocate gaps):** same two files (the `403 VM.Audit` config-read + the
|
||||
`403 VM.Allocate` scratch-teardown lines).
|
||||
- **H1 (dominant harness bug — auth-session expiry):** `evidence/T-STG-ENROLL/init1.txt`
|
||||
(`CSRF token missing or invalid`), `evidence/T-RST-VW/{restore_resp.txt,canary_check.txt}`
|
||||
(302→login, empty canary).
|
||||
- **H3 (D1 not delivered):** `overnight.out` around the `[D1]` block (`sqlite3: … not found`).
|
||||
- **Positives:** `evidence/T-PBS-VERIFY/`, `evidence/T-RESTORE-TEST-CRASH/`,
|
||||
`evidence/T-FLT-{KILL9,AGENT,CF}/`, `evidence/T-BAK-FULL/`, `evidence/T-AUTH-1/`, `evidence/T-CSRF/`.
|
||||
|
||||
## Verdict in one line
|
||||
One genuine pilot-critical product finding (R1/R2 — non-root can't restore a bind-mount guest);
|
||||
the rest of the FAIL count is a single dominant harness bug (H1) plus a few calibration issues; the
|
||||
product, where actually exercised, was healthy with zero resource leaks.
|
||||
@@ -0,0 +1 @@
|
||||
ct/9201/2026-07-07T06:11:58Z
|
||||
@@ -0,0 +1,32 @@
|
||||
2026-07-07T08:15:24+0200 [P7] [EXEC] verify R1
|
||||
verify datastore felhom-spike
|
||||
found 2 groups
|
||||
using 1 read and 4 verify thread(s)
|
||||
verify group felhom-spike:ct/9001 (1 snapshots)
|
||||
SKIPPED: verify felhom-spike:ct/9001/2026-06-09T15:01:37Z (recently verified)
|
||||
percentage done: 50.00% (1/2 groups)
|
||||
verify group felhom-spike:ct/9201 (9 snapshots)
|
||||
verify felhom-spike:ct/9201/2026-07-07T06:11:58Z
|
||||
check pct.conf.blob
|
||||
check root.pxar.didx
|
||||
verified 7080.68/20421.62 MiB in 66.36 seconds, speed 106.70/307.73 MiB/s (0 errors)
|
||||
check catalog.pcat1.didx
|
||||
verified 6.01/19.69 MiB in 0.20 seconds, speed 29.58/96.88 MiB/s (0 errors)
|
||||
percentage done: 55.56% (1/2 groups, 1/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-07-06T17:01:45Z (recently verified)
|
||||
percentage done: 61.11% (1/2 groups, 2/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-06-27T19:42:37Z (recently verified)
|
||||
percentage done: 66.67% (1/2 groups, 3/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-06-26T19:42:04Z (recently verified)
|
||||
percentage done: 72.22% (1/2 groups, 4/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-06-25T10:51:23Z (recently verified)
|
||||
percentage done: 77.78% (1/2 groups, 5/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-06-24T10:51:23Z (recently verified)
|
||||
percentage done: 83.33% (1/2 groups, 6/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-06-23T18:16:31Z (recently verified)
|
||||
percentage done: 88.89% (1/2 groups, 7/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-06-21T18:55:12Z (recently verified)
|
||||
percentage done: 94.44% (1/2 groups, 8/9 snapshots in group #2)
|
||||
SKIPPED: verify felhom-spike:ct/9201/2026-06-14T18:19:46Z (recently verified)
|
||||
percentage done: 100.00% (2/2 groups)
|
||||
TASK OK
|
||||
@@ -0,0 +1,62 @@
|
||||
2026-07-07T08:11:57+0200 [P7] [EXEC] R1 vzdump
|
||||
INFO: starting new backup job: vzdump 9201 --storage felhom-pbs --mode snapshot --notes-template campaign-R1
|
||||
INFO: Starting Backup of VM 9201 (lxc)
|
||||
INFO: Backup started at 2026-07-07 08:11:58
|
||||
INFO: status = running
|
||||
INFO: CT Name: demo-felhom
|
||||
INFO: including mount point rootfs ('/') in backup
|
||||
INFO: including mount point mp0 ('/var/lib/docker') in backup
|
||||
INFO: including mount point mp1 ('/mnt/sys_drive') in backup
|
||||
INFO: excluding bind mount point mp8 ('/mnt/felhom-drives') from backup (not a volume)
|
||||
INFO: excluding bind mount point mp9 ('/etc/felhom-bootstrap') from backup (not a volume)
|
||||
INFO: backup mode: snapshot
|
||||
INFO: ionice priority: 7
|
||||
INFO: suspend vm to make snapshot
|
||||
INFO: create storage snapshot 'vzdump'
|
||||
freeze guest filesystem
|
||||
WARNING: You have not turned on protection against thin pools running out of space.
|
||||
WARNING: Set activation/thin_pool_autoextend_threshold below 100 to trigger automatic extension of thin pools before they get full.
|
||||
Logical volume "snap_vm-9201-disk-0_vzdump" created.
|
||||
WARNING: Sum of all thin volume sizes (392.00 GiB) exceeds the size of thin pool pve/data and the amount of free space in volume group (16.00 GiB).
|
||||
WARNING: You have not turned on protection against thin pools running out of space.
|
||||
WARNING: Set activation/thin_pool_autoextend_threshold below 100 to trigger automatic extension of thin pools before they get full.
|
||||
Logical volume "snap_vm-9201-disk-1_vzdump" created.
|
||||
WARNING: Sum of all thin volume sizes (592.00 GiB) exceeds the size of thin pool pve/data and the size of whole volume group (<475.94 GiB).
|
||||
WARNING: You have not turned on protection against thin pools running out of space.
|
||||
WARNING: Set activation/thin_pool_autoextend_threshold below 100 to trigger automatic extension of thin pools before they get full.
|
||||
Logical volume "snap_vm-9201-disk-2_vzdump" created.
|
||||
WARNING: Sum of all thin volume sizes (642.00 GiB) exceeds the size of thin pool pve/data and the size of whole volume group (<475.94 GiB).
|
||||
thaw guest filesystem
|
||||
INFO: resume vm
|
||||
INFO: guest is online again after 2 seconds
|
||||
INFO: creating Proxmox Backup Server archive 'ct/9201/2026-07-07T06:11:58Z'
|
||||
INFO: set max number of entries in memory for file-based backups to 1048576
|
||||
INFO: run: lxc-usernsexec -m u:0:100000:65536 -m g:0:100000:65536 -- /usr/bin/proxmox-backup-client backup --crypt-mode=encrypt --keyfd=12 pct.conf:/var/tmp/vzdumptmp3489147_9201/etc/vzdump/pct.conf root.pxar:/mnt/vzsnap0 --include-dev /mnt/vzsnap0/./ --include-dev /mnt/vzsnap0/./var/lib/docker --include-dev /mnt/vzsnap0/./mnt/sys_drive --skip-lost-and-found --exclude=/tmp/?* --exclude=/var/tmp/?* --exclude=/var/run/?*.pid --backup-type ct --backup-id 9201 --backup-time 1783404718 --entries-max 1048576 --repository felhom@pbs!n100@192.168.0.180:felhom-spike
|
||||
INFO: Starting backup: ct/9201/2026-07-07T06:11:58Z
|
||||
INFO: Client name: demo-felhom
|
||||
INFO: Starting backup protocol: Tue Jul 7 08:12:00 2026
|
||||
INFO: Using encryption key from file descriptor..
|
||||
INFO: Encryption key fingerprint: 01:36:e9:fe:e1:ee:3d:7a
|
||||
INFO: Downloading previous manifest (Tue Jul 7 07:28:19 2026)
|
||||
INFO: Upload config file '/var/tmp/vzdumptmp3489147_9201/etc/vzdump/pct.conf' to 'felhom@pbs!n100@192.168.0.180:8007:felhom-spike' as pct.conf.blob
|
||||
INFO: Upload directory '/mnt/vzsnap0' to 'felhom@pbs!n100@192.168.0.180:8007:felhom-spike' as root.pxar.didx
|
||||
INFO: processed 6.594 GiB in 1m, uploaded 168.758 MiB
|
||||
INFO: processed 12.221 GiB in 2m, uploaded 204.992 MiB
|
||||
INFO: processed 18.596 GiB in 3m, uploaded 933.24 MiB
|
||||
INFO: root.pxar: had to backup 1.257 GiB of 20.802 GiB (compressed 352.15 MiB) in 200.75 s (average 6.409 MiB/s)
|
||||
INFO: root.pxar: backup was done incrementally, reused 19.545 GiB (94.0%)
|
||||
INFO: Uploaded backup catalog (19.692 MiB)
|
||||
INFO: Duration: 202.12s
|
||||
INFO: End Time: Tue Jul 7 08:15:22 2026
|
||||
INFO: adding notes to backup
|
||||
INFO: prune older backups with retention: keep-daily=7, keep-monthly=3, keep-weekly=4
|
||||
INFO: running 'proxmox-backup-client prune' for 'ct/9201'
|
||||
INFO: pruned 1 backup(s) not covered by keep-retention policy
|
||||
INFO: cleanup temporary 'vzdump' snapshot
|
||||
Logical volume "snap_vm-9201-disk-0_vzdump" successfully removed.
|
||||
Logical volume "snap_vm-9201-disk-1_vzdump" successfully removed.
|
||||
Logical volume "snap_vm-9201-disk-2_vzdump" successfully removed.
|
||||
INFO: Finished Backup of VM 9201 (00:03:26)
|
||||
INFO: Backup finished at 2026-07-07 08:15:24
|
||||
INFO: Backup job finished successfully
|
||||
INFO: notified via target `mail-to-root`
|
||||
@@ -0,0 +1 @@
|
||||
no-session /api/backup/status -> 401
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bejelentkezés — Felhom</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body class="login-body">
|
||||
<div class="login-card">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="login-logo">
|
||||
<h1 class="login-title">Otthoni <span class="title-accent">vezérlőpult</span></h1>
|
||||
<p class="login-subtitle">Demo Ügyfél</p>
|
||||
|
||||
<div class="alert alert-error">Hibás jelszó</div>
|
||||
<form method="POST" action="/login">
|
||||
<input type="hidden" name="next" id="next-field" value="">
|
||||
<div class="form-group">
|
||||
<label for="password">Jelszó</label>
|
||||
<input type="password" id="password" name="password" required autofocus
|
||||
placeholder="Adja meg a jelszavát" class="form-control">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-full">Bejelentkezés</button>
|
||||
</form>
|
||||
<p class="login-footer">Felhom — Otthoni szerver kezelés<br>
|
||||
<a href="https://felhom.eu" target="_blank">felhom.eu</a></p>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var next = params.get('next');
|
||||
if (next) {
|
||||
document.getElementById('next-field').value = next;
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,2 @@
|
||||
vaultwarden api=1 ondisk_files=12
|
||||
paperless-ngx api=1 ondisk_files=12
|
||||
|
+1
@@ -0,0 +1 @@
|
||||
{"ok":true,"data":[{"time":"2026-07-07T05:22:18Z","short_id":"helyi","tier":1,"drive_label":"Tárhely (felhom-usb)"}]}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":true,"data":[{"time":"2026-07-07T05:23:13Z","short_id":"helyi","tier":1,"drive_label":""}]}
|
||||
@@ -0,0 +1,4 @@
|
||||
paperless-ngx db-volume-tar /mnt/felhom-drives/felhom-usb/backups/primary/paperless-ngx/volume-dumps/paperless-ngx_paperless_postgres_data.tar size=70932992
|
||||
bookstack /mnt/sys_drive/felhom-data/backups/primary/bookstack/db-dumps/bookstack-mariadb.sql
|
||||
paperless-ngx db-volume-tar /mnt/felhom-drives/felhom-usb/backups/primary/paperless-ngx/volume-dumps/paperless-ngx_paperless_postgres_data.tar size=71001600
|
||||
bookstack /mnt/sys_drive/felhom-data/backups/primary/bookstack/db-dumps/bookstack-mariadb.sql
|
||||
|
@@ -0,0 +1 @@
|
||||
bookstack val= want_sha= got_sha=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
|
@@ -0,0 +1 @@
|
||||
/mnt/sys_drive/felhom-data/backups
|
||||
@@ -0,0 +1,3 @@
|
||||
{"ok":true,"message":"Mentés elindítva"}
|
||||
|
||||
200
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":true,"data":{"db_dump":{"count":7,"duration":"2m22.468048466s","last_run":"2026-07-07T05:23:13.542657361Z","success":true},"enabled":true,"running":false}}
|
||||
@@ -0,0 +1,10 @@
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary/nextcloud
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary/nextcloud/appdata
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary/nextcloud/appdata/admin
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary/nextcloud/appdata/admin/files
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary/nextcloud/appdata/admin/files/Documents
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary/nextcloud/appdata/admin/files/Photos
|
||||
/mnt/felhom-drives/felhom-usb/backups/secondary/nextcloud/appdata/admin/files/Templates
|
||||
/mnt/felhom-drives/felhom-flash/backups/secondary
|
||||
/mnt/felhom-drives/felhom-flash/backups/secondary/nextcloud
|
||||
@@ -0,0 +1,2 @@
|
||||
vaultwarden tar=/mnt/sys_drive/felhom-data/backups/primary/vaultwarden/volume-dumps/vaultwarden_vaultwarden_data.tar want=dffbaa0f0e85f824cf3723610824fac5dcadcd827a473b3631937cfeedcf469f got=dffbaa0f0e85f824cf3723610824fac5dcadcd827a473b3631937cfeedcf469f
|
||||
uptime-kuma tar=/mnt/sys_drive/felhom-data/backups/primary/uptime-kuma/volume-dumps/uptime-kuma_uptime_kuma_data.tar want=30ecfc2ec844d4e536f57609ba2e472288befa1598fa465c1268b61d8608c4af got=30ecfc2ec844d4e536f57609ba2e472288befa1598fa465c1268b61d8608c4af
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
ct/9001
|
||||
ct/9201
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{"ok":true,"message":"Mentés elindítva"}
|
||||
|
||||
200
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{"ok":false,"error":"Mentés már folyamatban"}
|
||||
|
||||
409
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
vaultwarden hdd hdd_vaultwarden 64c97707c7a8be3dd376f11bbe2e904c52eec6f2fa24065ff52293866dc1448b
|
||||
vaultwarden vol vol_vaultwarden_vaultwarden_data dffbaa0f0e85f824cf3723610824fac5dcadcd827a473b3631937cfeedcf469f
|
||||
uptime-kuma hdd hdd_uptime-kuma 29fac3042f48f6c12c722f9e098a463cb955dc09699f0c4e79094e98c60980df
|
||||
uptime-kuma vol vol_uptime-kuma_uptime_kuma_data 30ecfc2ec844d4e536f57609ba2e472288befa1598fa465c1268b61d8608c4af
|
||||
actualbudget SKIP stopped
|
||||
paperless-ngx hdd hdd_paperless-ngx e03cf53f9c1efdaef3125709fc34c84de9bd3d1a2343fd0ca841a01c1e196781
|
||||
paperless-ngx vol vol_paperless-ngx_paperless_data ab805ecdc6a740a546908b6c09cc68245d06a296f5bf599804cfca80d7341140
|
||||
paperless-ngx vol vol_paperless-ngx_paperless_postgres_data 925dd8fcb0499059f7ef476b3f22b7a80aea55a370fd78f993042a154cb6ea5e
|
||||
paperless-ngx vol vol_paperless-ngx_paperless_redis_data 2f4dac652953af674a1992a652f2f3d637539d80796beaea2af5116d79a0b1dc
|
||||
mealie SKIP stopped
|
||||
bookstack SKIP stopped
|
||||
navidrome SKIP stopped
|
||||
paperless-ngx db SKIP stopped
|
||||
bookstack db SKIP stopped
|
||||
mealie db SKIP stopped
|
||||
|
@@ -0,0 +1 @@
|
||||
no-token=403 with-token=200
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
vaultwarden running http=200
|
||||
uptime-kuma running http=200
|
||||
actualbudget running http=200
|
||||
paperless-ngx running http=200
|
||||
mealie running http=200
|
||||
bookstack running http=200
|
||||
navidrome running http=200
|
||||
|
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"values": {"SUBDOMAIN": "actual", "subdomain": "actual"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"values": {"SUBDOMAIN": "books", "APP_KEY": "UKvcjR7thRDsfnRCZqqOH2rl22A7", "DB_PASSWORD": "UKvcjR7thRDsfnRCZqqOH2rl22A7", "subdomain": "books"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"values": {"SUBDOMAIN": "mealie", "subdomain": "mealie"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"values": {"SUBDOMAIN": "music", "HDD_PATH": "/mnt/felhom-drives/felhom-usb", "subdomain": "music"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"values": {"SUBDOMAIN": "docs", "DB_PASSWORD": "TxkE6Kz0hRsI5xx7ZZEwXI11xoYe", "PAPERLESS_SECRET_KEY": "TxkE6Kz0hRsI5xx7ZZEwXI11xoYe", "PAPERLESS_ADMIN_PASSWORD": "TxkE6Kz0hRsI5xx7ZZEwXI11xoYe", "HDD_PATH": "/mnt/felhom-drives/felhom-usb", "subdomain": "docs"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"values": {"SUBDOMAIN": "uptime", "subdomain": "uptime"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"values": {"SUBDOMAIN": "vault", "ADMIN_TOKEN": "3BJkfTB6iEJeBhCORK6e9oazB0zK", "subdomain": "vault"}}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
vaultwarden: running restarts=0
|
||||
SMTP_FROM=
|
||||
SMTP_HOST=
|
||||
_ENABLE_SMTP=false
|
||||
@@ -0,0 +1,3 @@
|
||||
agent_active=active enrolled_bound=/mnt/felhom-drives/felhom-flash /dev/sdd1[/felhom-data] ext4 rw,relatime
|
||||
/mnt/felhom-drives/felhom-flash /dev/sdd1[/felhom-data] ext4 rw,relatime
|
||||
bound recover_s=5
|
||||
@@ -0,0 +1,2 @@
|
||||
baseline=302 down=302
|
||||
recovered=302 recover_s=20
|
||||
@@ -0,0 +1 @@
|
||||
oob_8822=closed
|
||||
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
stack_state=? app_restartcount=0
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
vaultwarden=?
|
||||
? paperless=?
|
||||
? recover=623s
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
401
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
401
|
||||
@@ -0,0 +1 @@
|
||||
ts_before= ts_after=
|
||||
@@ -0,0 +1 @@
|
||||
restarts_during_outage=
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
{"ok":true,"message":"Mentés elindítva"}
|
||||
|
||||
200
|
||||
+1
@@ -0,0 +1 @@
|
||||
no-repo
|
||||
@@ -0,0 +1 @@
|
||||
recovery: detect=2s
|
||||
+1
@@ -0,0 +1 @@
|
||||
copied_before_stop=
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":true,"data":{"logs":""}}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
@@ -0,0 +1,5 @@
|
||||
2026-07-07T14:10:44+0200 [P9DR] [EXEC] cancel revert timers
|
||||
2026-07-07T14:10:44+0200 [P9DR] [EXEC] unblock hub/gitea
|
||||
2026-07-07T14:10:45+0200 [P9DR] [EXEC] reauthorize sdb
|
||||
2026-07-07T14:10:45+0200 [P9DR] [EXEC] remove fillers
|
||||
2026-07-07T14:10:46+0200 [P9DR] [EXEC] kill stress-ng
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
sparkyfitness-server Up 5 seconds (health: starting)
|
||||
sparkyfitness-db Up 11 seconds (healthy)
|
||||
rallly Up 12 seconds (healthy)
|
||||
rallly-postgres Up 18 seconds (healthy)
|
||||
paperless-webserver Up 19 seconds (health: starting)
|
||||
paperless-postgres Up 29 seconds (healthy)
|
||||
paperless-redis Up 29 seconds (healthy)
|
||||
nextcloud Up 30 seconds (healthy)
|
||||
nextcloud-db Up 36 seconds (healthy)
|
||||
nextcloud-redis Up 36 seconds (healthy)
|
||||
navidrome Up 36 seconds (healthy)
|
||||
mealie Up 37 seconds (healthy)
|
||||
gitea Up 38 seconds (healthy)
|
||||
calcom Up 38 seconds (health: starting)
|
||||
calcom-postgres Up 44 seconds (healthy)
|
||||
bookstack Up 44 seconds (health: starting)
|
||||
bookstack-db Up 50 seconds (healthy)
|
||||
actualbudget Up 50 seconds (healthy)
|
||||
filebrowser Up 6 minutes (healthy)
|
||||
felhom-controller Up 6 minutes (healthy)
|
||||
cloudflared Up 6 minutes
|
||||
traefik Up 6 minutes
|
||||
@@ -0,0 +1,28 @@
|
||||
NAME SIZE TYPE TRAN MOUNTPOINTS
|
||||
sda 476.9G disk sata
|
||||
├─sda1 1007K part
|
||||
├─sda2 1G part /boot/efi
|
||||
└─sda3 475.9G part
|
||||
├─pve-swap 8G lvm [SWAP]
|
||||
├─pve-root 96G lvm /mnt/felhom-drives
|
||||
│ /
|
||||
├─pve-data_tmeta 3.6G lvm
|
||||
│ └─pve-data-tpool 348.8G lvm
|
||||
│ ├─pve-data 348.8G lvm
|
||||
│ ├─pve-vm--9201--disk--0 32G lvm
|
||||
│ ├─pve-vm--9201--disk--1 200G lvm
|
||||
│ └─pve-vm--9201--disk--2 50G lvm
|
||||
└─pve-data_tdata 348.8G lvm
|
||||
└─pve-data-tpool 348.8G lvm
|
||||
├─pve-data 348.8G lvm
|
||||
├─pve-vm--9201--disk--0 32G lvm
|
||||
├─pve-vm--9201--disk--1 200G lvm
|
||||
└─pve-vm--9201--disk--2 50G lvm
|
||||
sdb 59.5G disk usb /mnt/felhom-drives/teszt_enroll
|
||||
/mnt/teszt_enroll
|
||||
sdc 119.5G disk usb
|
||||
└─sdc1 119.5G part /mnt/felhom-drives/felhom-flash
|
||||
/mnt/felhom-flash
|
||||
sdd 931.5G disk usb
|
||||
└─sdd1 931.5G part /mnt/felhom-drives/felhom-usb
|
||||
/mnt/felhom-usb
|
||||
+1
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
@@ -0,0 +1 @@
|
||||
report written: /home/kisfenyo/campaign2-nomercy/harness/CAMPAIGN-nomercy-2026-07-06.md
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/mnt/felhom-drives /dev/mapper/pve-root[/mnt/felhom-drives]
|
||||
/mnt/felhom-drives/felhom-flash /dev/sdc1[/felhom-data]
|
||||
/mnt/felhom-drives/felhom-usb /dev/sdd1[/felhom-data]
|
||||
/mnt/felhom-drives/teszt_enroll /dev/sdb[/felhom-data]
|
||||
/mnt/felhom-drives /dev/mapper/pve-root[/mnt/felhom-drives]
|
||||
/mnt/felhom-drives/felhom-flash /dev/sdc1[/felhom-data]
|
||||
/mnt/felhom-drives/felhom-usb /dev/sdd1[/felhom-data]
|
||||
/mnt/felhom-drives/teszt_enroll /dev/sdb[/felhom-data]
|
||||
+1
@@ -0,0 +1 @@
|
||||
restore snapshot: ct/9201/2026-07-07T06:11:58Z (post-snapshot writes expected lost)
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
2026-07-07T13:59:38+0200 [P9] [EXEC] pct stop 9201
|
||||
2026-07-07T13:59:42+0200 [P9] [EXEC] pct destroy 9201
|
||||
Logical volume "vm-9201-disk-0" successfully removed.
|
||||
Logical volume "vm-9201-disk-1" successfully removed.
|
||||
Logical volume "vm-9201-disk-2" successfully removed.
|
||||
2026-07-07T13:59:43+0200 [P9] [EXEC] pct restore 9201
|
||||
recovering backed-up configuration from 'felhom-pbs:backup/ct/9201/2026-07-07T06:11:58Z'
|
||||
Using encryption key from file descriptor..
|
||||
Fingerprint: 01:36:e9:fe:e1:ee:3d:7a
|
||||
Logical volume "vm-9201-disk-0" created.
|
||||
Logical volume pve/vm-9201-disk-0 changed.
|
||||
Creating filesystem with 8388608 4k blocks and 2097152 inodes
|
||||
Filesystem UUID: 859e7906-dd0d-49b7-a7b6-1cf7c8477881
|
||||
Superblock backups stored on blocks:
|
||||
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
|
||||
4096000, 7962624
|
||||
Logical volume "vm-9201-disk-1" created.
|
||||
Logical volume pve/vm-9201-disk-1 changed.
|
||||
Creating filesystem with 52428800 4k blocks and 13107200 inodes
|
||||
Filesystem UUID: 0a4aae08-bba4-472a-80f7-b3b2f31c8e30
|
||||
Superblock backups stored on blocks:
|
||||
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
|
||||
4096000, 7962624, 11239424, 20480000, 23887872
|
||||
WARNING: You have not turned on protection against thin pools running out of space.
|
||||
WARNING: Set activation/thin_pool_autoextend_threshold below 100 to trigger automatic extension of thin pools before they get full.
|
||||
Logical volume "vm-9201-disk-2" created.
|
||||
WARNING: Sum of all thin volume sizes (360.00 GiB) exceeds the size of thin pool pve/data and the amount of free space in volume group (16.00 GiB).
|
||||
Logical volume pve/vm-9201-disk-2 changed.
|
||||
Creating filesystem with 13107200 4k blocks and 3276800 inodes
|
||||
Filesystem UUID: 191cdec2-ea88-41de-b5fe-beb5e502432f
|
||||
Superblock backups stored on blocks:
|
||||
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
|
||||
4096000, 7962624, 11239424
|
||||
restoring 'felhom-pbs:backup/ct/9201/2026-07-07T06:11:58Z' now..
|
||||
Using encryption key from file descriptor..
|
||||
Fingerprint: 01:36:e9:fe:e1:ee:3d:7a
|
||||
merging backed-up and given configuration..
|
||||
Using encryption key from file descriptor..
|
||||
Fingerprint: 01:36:e9:fe:e1:ee:3d:7a
|
||||
2026-07-07T14:03:55+0200 [P9] [EXEC] pct start 9201
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,936 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vezérlőpult — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name">Demo Ügyfél</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class="active"><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.102.0</span>
|
||||
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
<div class="alerts-container">
|
||||
|
||||
|
||||
<div class="alert-banner alert-banner-error">
|
||||
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
|
||||
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
|
||||
<a href="/settings" class="alert-link">Beállítások →</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Vezérlőpult</h2>
|
||||
<span class="domain-badge">demo-felhom.eu</span>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<a href="/stacks?filter=running" class="stat-card">
|
||||
<div class="stat-value">15</div>
|
||||
<div class="stat-label">Futó alkalmazás</div>
|
||||
</a>
|
||||
<a href="/stacks?filter=stopped" class="stat-card stat-zero">
|
||||
<div class="stat-value">0</div>
|
||||
<div class="stat-label">Leállítva</div>
|
||||
</a>
|
||||
<a href="/stacks" class="stat-card">
|
||||
<div class="stat-value">56</div>
|
||||
<div class="stat-label">Összes alkalmazás</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="system-info-card">
|
||||
<div class="system-info-items">
|
||||
<div class="system-info-item meter nominal">
|
||||
<div class="system-info-header">
|
||||
<span class="system-info-label">Memória</span>
|
||||
<span class="system-info-value">3.7 GB / 12 GB (31%)</span>
|
||||
</div>
|
||||
<div class="meter-track">
|
||||
<div class="meter-fill" style="width:31%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="system-info-item meter nominal">
|
||||
<div class="system-info-header">
|
||||
<span class="system-info-label">CPU</span>
|
||||
<span class="system-info-value">2%</span>
|
||||
</div>
|
||||
<div class="meter-track">
|
||||
<div class="meter-fill" style="width:2%"></div>
|
||||
</div>
|
||||
<div class="system-load-avg">Load: 0.75 / 1.87 / 1.94</div>
|
||||
</div>
|
||||
|
||||
<div class="system-info-item system-info-item-compact">
|
||||
<div class="system-info-header">
|
||||
<span class="system-info-label"><svg class="ico ico-sm"><use href="#i-thermometer"/></svg> Hőmérséklet</span>
|
||||
<span class="system-info-value temp-value state-text-nominal">51°C</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="system-info-items" style="margin-top: 1rem;">
|
||||
|
||||
<div class="system-info-item meter nominal">
|
||||
<div class="system-info-header">
|
||||
<span class="system-info-label">Rendszer (/)</span>
|
||||
<span class="system-info-value">20.9 GB / 196 GB (11%)</span>
|
||||
</div>
|
||||
<div class="meter-track">
|
||||
<div class="meter-fill" style="width:11%"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="system-info-item meter nominal">
|
||||
<div class="system-info-header">
|
||||
<span class="system-info-label">Tárhely (felhom-flash)</span>
|
||||
<span class="system-info-value">4.02 GB / 117 GB (3%)</span>
|
||||
</div>
|
||||
<div class="meter-track">
|
||||
<div class="meter-fill" style="width:3%"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="system-info-item meter nominal">
|
||||
<div class="system-info-header">
|
||||
<span class="system-info-label">Tárhely (felhom-usb)</span>
|
||||
<span class="system-info-value">1.21 GB / 916 GB (0%)</span>
|
||||
</div>
|
||||
<div class="meter-track">
|
||||
<div class="meter-fill" style="width:0%"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="system-info-item meter nominal">
|
||||
<div class="system-info-header">
|
||||
<span class="system-info-label">teszt_enroll</span>
|
||||
<span class="system-info-value">0.00 GB / 58.2 GB (0%)</span>
|
||||
</div>
|
||||
<div class="meter-track">
|
||||
<div class="meter-fill" style="width:0%"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="backup-status-card">
|
||||
<h3><a href="/backups" class="backup-card-link">Biztonsági mentés</a></h3>
|
||||
|
||||
<div class="backup-info-row">
|
||||
<span class="backup-label">Utolsó mentés:</span>
|
||||
<span class="backup-value backup-status-none">Még nem futott</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="backup-info-row">
|
||||
<span class="backup-label">Adatbázisok:</span>
|
||||
<span class="backup-value">5 mentve</span>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<h3>Telepített alkalmazások</h3>
|
||||
|
||||
<div class="stack-list">
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/actualbudget">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/actualbudget-logo.svg"
|
||||
alt="ActualBudget" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/actualbudget-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">ActualBudget</strong>
|
||||
<span class="stack-desc">Személyes pénzügyek és költségvetés kezelése</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://actual.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'actualbudget', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'actualbudget', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/actualbudget/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-warn" data-href="/apps/bookstack">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/bookstack-logo.svg"
|
||||
alt="BookStack" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/bookstack-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">BookStack</strong>
|
||||
<span class="stack-desc">Egyszerű, könyv-szerű wiki és dokumentáció platform</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-warn"><span class="dot"></span>Nem egészséges</span>
|
||||
|
||||
|
||||
|
||||
<span class="tag tag-warn" title="A proxy (Traefik) csak egészséges konténerhez publikál nyilvános útvonalat. Amíg az alkalmazás nem egészséges, az URL 404-et ad, pedig a konténer fut."><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg>URL nem elérhető</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://books.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'bookstack', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'bookstack', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/bookstack/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/calcom">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/calcom-logo.svg"
|
||||
alt="Cal.com" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/calcom-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Cal.com</strong>
|
||||
<span class="stack-desc">Nyílt forráskódú időpontfoglaló (Calendly alternatíva)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://cal.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'calcom', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'calcom', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/calcom/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/cloudflared">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/cloudflared-logo.svg"
|
||||
alt="Cloudflared" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/cloudflared-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Cloudflared</strong>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="tag"><svg class="ico ico-sm"><use href="#i-lock"/></svg>Védett</span>
|
||||
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'cloudflared', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/filebrowser">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/filebrowser-logo.svg"
|
||||
alt="Filebrowser" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/filebrowser-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Filebrowser</strong>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="tag"><svg class="ico ico-sm"><use href="#i-lock"/></svg>Védett</span>
|
||||
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'filebrowser', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/gitea">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/gitea-logo.svg"
|
||||
alt="Gitea" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/gitea-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Gitea</strong>
|
||||
<span class="stack-desc">Könnyű, saját Git szerver webes felülettel</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://git.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'gitea', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'gitea', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/gitea/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/mealie">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/mealie-logo.svg"
|
||||
alt="Mealie" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/mealie-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Mealie</strong>
|
||||
<span class="stack-desc">Receptkezelő és étkezéstervező</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://mealie.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'mealie', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'mealie', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/mealie/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/navidrome">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/navidrome-logo.svg"
|
||||
alt="Navidrome" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/navidrome-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Navidrome</strong>
|
||||
<span class="stack-desc">Könnyű zene szerver Subsonic API támogatással</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://music.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'navidrome', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'navidrome', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/navidrome/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/nextcloud">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/nextcloud-logo.svg"
|
||||
alt="Nextcloud" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/nextcloud-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Nextcloud</strong>
|
||||
<span class="stack-desc">Saját felhő tárhely - Google Drive/Dropbox alternatíva</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://cloud.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'nextcloud', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'nextcloud', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/nextcloud/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/paperless-ngx">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/paperless-ngx-logo.svg"
|
||||
alt="Paperless-ngx" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/paperless-ngx-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Paperless-ngx</strong>
|
||||
<span class="stack-desc">Dokumentumok digitalizálása és rendszerezése</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://docs.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'paperless-ngx', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'paperless-ngx', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/paperless-ngx/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/rallly">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/rallly-logo.svg"
|
||||
alt="Rallly" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/rallly-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Rallly</strong>
|
||||
<span class="stack-desc">Időpont szavazás (Doodle alternatíva)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://poll.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'rallly', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'rallly', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/rallly/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/sparkyfitness">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/sparkyfitness-logo.svg"
|
||||
alt="SparkyFitness" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/sparkyfitness-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">SparkyFitness</strong>
|
||||
<span class="stack-desc">Táplálkozás- és edzéskövető</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://sparky.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'sparkyfitness', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'sparkyfitness', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/sparkyfitness/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/traefik">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/traefik-logo.svg"
|
||||
alt="Traefik" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/traefik-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Traefik</strong>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="tag"><svg class="ico ico-sm"><use href="#i-lock"/></svg>Védett</span>
|
||||
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'traefik', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/uptime-kuma">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/uptime-kuma-logo.svg"
|
||||
alt="Uptime Kuma" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/uptime-kuma-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Uptime Kuma</strong>
|
||||
<span class="stack-desc">Szolgáltatás és weboldal monitoring</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://status.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'uptime-kuma', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'uptime-kuma', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/uptime-kuma/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stack-card stack-state-run" data-href="/apps/vaultwarden">
|
||||
<div class="stack-info">
|
||||
<img class="stack-logo" src="/static/assets/vaultwarden-logo.svg"
|
||||
alt="Vaultwarden" onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/vaultwarden-logo.png'">
|
||||
<div>
|
||||
<strong class="stack-name">Vaultwarden</strong>
|
||||
<span class="stack-desc">Jelszókezelő (Bitwarden-kompatibilis)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack-actions">
|
||||
<span class="tag tag-run"><span class="dot"></span>Fut</span>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a href="https://vault.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline" onclick="event.stopPropagation()">Megnyitás <svg class="ico ico-sm"><use href="#i-external-link"/></svg></a>
|
||||
<button class="btn btn-sm btn-warning" onclick="stackAction(event, 'vaultwarden', 'restart')" title="Újraindítás"><svg class="ico ico-sm"><use href="#i-rotate-cw"/></svg></button>
|
||||
<button class="btn btn-sm btn-danger" onclick="stackAction(event, 'vaultwarden', 'stop')" title="Leállítás"><svg class="ico ico-sm"><use href="#i-square"/></svg></button>
|
||||
|
||||
<a href="/stacks/vaultwarden/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+524
@@ -0,0 +1,524 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Paperless-ngx — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name">Demo Ügyfél</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.102.0</span>
|
||||
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
<div class="alerts-container">
|
||||
|
||||
|
||||
<div class="alert-banner alert-banner-error">
|
||||
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
|
||||
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
|
||||
<a href="/settings" class="alert-link">Beállítások →</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div style="display:flex;align-items:center;gap:1rem">
|
||||
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
|
||||
<h2>Paperless-ngx</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
|
||||
<span class="stack-state-badge state-run">Fut</span>
|
||||
|
||||
<a href="https://docs.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
|
||||
<a href="/stacks/paperless-ngx/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
<a href="/stacks/paperless-ngx/export" class="btn btn-sm btn-outline">Exportálás</a>
|
||||
<a href="/stacks/paperless-ngx/deploy" class="btn btn-sm btn-outline">Beállítások</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-logo" src="/static/assets/paperless-ngx-logo.svg"
|
||||
alt="Paperless-ngx"
|
||||
onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/paperless-ngx-logo.png'">
|
||||
<div class="app-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">Digitális irattár - szkennelés, OCR és automatikus rendszerezés</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~500M RAM</span>
|
||||
<span class="meta-badge">productivity</span>
|
||||
<span class="meta-badge meta-badge-warn">HDD szükséges</span>
|
||||
<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/paperless-ngx-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Áthelyezés másik tárhelyre</h3>
|
||||
<p class="form-hint">Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át. Az alkalmazás az áthelyezés alatt rövid időre leáll, az adatok pedig csak az ellenőrzés és a sikeres újraindítás után törlődnek a régi helyről.</p>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
|
||||
<select id="app-migrate-target" class="btn btn-sm btn-outline">
|
||||
<option value="">Válassz céltárhelyet…</option>
|
||||
<option value="/mnt/felhom-drives/felhom-flash">Tárhely (felhom-flash) (/mnt/felhom-drives/felhom-flash)</option><option value="/mnt/felhom-drives/teszt_enroll">teszt_enroll (/mnt/felhom-drives/teszt_enroll)</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate('paperless-ngx','Paperless-ngx')">Áthelyezés</button>
|
||||
</div>
|
||||
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
|
||||
</div>
|
||||
<script>
|
||||
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
|
||||
function appMigRender(job){
|
||||
var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'};
|
||||
var s=names[job.phase]||job.phase;
|
||||
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
|
||||
return s+'…';
|
||||
}
|
||||
function appMigWatch(){
|
||||
var panel=document.getElementById('app-migrate-progress');
|
||||
if(panel) panel.style.display='block';
|
||||
function tick(){
|
||||
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
|
||||
var job=d.data&&d.data.job;
|
||||
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
|
||||
if(panel) panel.innerHTML=appMigRender(job);
|
||||
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
|
||||
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
|
||||
setTimeout(tick,1500);
|
||||
}).catch(function(){ setTimeout(tick,2000); });
|
||||
}
|
||||
tick();
|
||||
}
|
||||
function appMigrate(app,label){
|
||||
var sel=document.getElementById('app-migrate-target');
|
||||
var target=sel?sel.value:'';
|
||||
if(!target){ alert('Válassz céltárhelyet.'); return; }
|
||||
if(!confirm('Áthelyezed a(z) '+label+' adatait ide: '+target+'?\n\nAz alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.')) return;
|
||||
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { alert('Hiba: '+(d.error||'ismeretlen')); } })
|
||||
.catch(function(e){ alert('Hiba: '+e); });
|
||||
}
|
||||
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Mire használható?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Papír dokumentumok digitalizálása és rendszerezése</li><li>Automatikus OCR szövegfelismerés szkennelt dokumentumokon</li><li>Intelligens automatikus kategorizálás és címkézés</li><li>Teljes szöveges keresés az összes dokumentumban</li><li>Email-ből érkező dokumentumok automatikus feldolgozása</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Első lépések</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Nyisd meg a paperless.DOMAIN címet a böngészőben</li><li>Jelentkezz be: admin / admin</li><li>Változtasd meg azonnal a jelszót</li><li>Tölts fel egy dokumentumot a webfelületen, VAGY dobd a Fájlkezelőben (FileBrowser) az import/paperless mappába — onnan automatikusan beolvassa és OCR-rel feldolgozza</li><li>Hozz létre címkéket és levelezőket az automatikus rendszerezéshez</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Előfeltételek</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Külső HDD ajánlott a dokumentumok tárolásához</li><li>Legalább 1 GB szabad RAM (OCR feldolgozáshoz)</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Alapértelmezett belépés</h3>
|
||||
<p class="app-info-creds">admin / admin</p>
|
||||
<p class="app-info-creds-warn">Az első bejelentkezés után azonnal változtasd meg!</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Dokumentáció</h3>
|
||||
<p><a href="https://docs.paperless-ngx.com/" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+511
@@ -0,0 +1,511 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vaultwarden — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name">Demo Ügyfél</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class="active"><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.102.0</span>
|
||||
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
<div class="alerts-container">
|
||||
|
||||
|
||||
<div class="alert-banner alert-banner-error">
|
||||
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
|
||||
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
|
||||
<a href="/settings" class="alert-link">Beállítások →</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div style="display:flex;align-items:center;gap:1rem">
|
||||
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
|
||||
<h2>Vaultwarden</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
|
||||
<span class="stack-state-badge state-run">Fut</span>
|
||||
|
||||
<a href="https://vault.demo-felhom.eu" target="_blank" class="btn btn-sm btn-outline">Megnyitás ↗</a>
|
||||
<a href="/stacks/vaultwarden/logs" class="btn btn-sm btn-outline">Napló</a>
|
||||
|
||||
<a href="/stacks/vaultwarden/export" class="btn btn-sm btn-outline">Exportálás</a>
|
||||
<a href="/stacks/vaultwarden/deploy" class="btn btn-sm btn-outline">Beállítások</a>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-logo" src="/static/assets/vaultwarden-logo.svg"
|
||||
alt="Vaultwarden"
|
||||
onerror="this.onerror=function(){this.style.display='none'};this.src='\/static\/assets\/vaultwarden-logo.png'">
|
||||
<div class="app-info-hero-text">
|
||||
|
||||
<p class="app-info-tagline">Jelszókezelő - Bitwarden kompatibilis, a saját szerveren</p>
|
||||
|
||||
<div class="stack-meta-badges">
|
||||
<span class="meta-badge">~50M RAM</span>
|
||||
<span class="meta-badge">security</span>
|
||||
|
||||
<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/vaultwarden-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/vaultwarden-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
<img src="/static/assets/vaultwarden-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<h3>Áthelyezés másik tárhelyre</h3>
|
||||
<p class="form-hint">Ennek az alkalmazásnak az adatait másik csatlakoztatott tárhelyre helyezheted át. Az alkalmazás az áthelyezés alatt rövid időre leáll, az adatok pedig csak az ellenőrzés és a sikeres újraindítás után törlődnek a régi helyről.</p>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
|
||||
<select id="app-migrate-target" class="btn btn-sm btn-outline">
|
||||
<option value="">Válassz céltárhelyet…</option>
|
||||
<option value="/mnt/felhom-drives/felhom-usb">Tárhely (felhom-usb) (/mnt/felhom-drives/felhom-usb)</option><option value="/mnt/felhom-drives/felhom-flash">Tárhely (felhom-flash) (/mnt/felhom-drives/felhom-flash)</option><option value="/mnt/felhom-drives/teszt_enroll">teszt_enroll (/mnt/felhom-drives/teszt_enroll)</option>
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline" onclick="appMigrate('vaultwarden','Vaultwarden')">Áthelyezés</button>
|
||||
</div>
|
||||
<div id="app-migrate-progress" style="display:none;margin-top:.75rem;padding:.75rem;border:1px solid var(--blue);border-radius:6px;background:rgba(0,136,204,0.06)"></div>
|
||||
</div>
|
||||
<script>
|
||||
function appMigFmtGB(b){ return (Number(b||0)/1e9).toFixed(1)+' GB'; }
|
||||
function appMigRender(job){
|
||||
var names={stop:'Leállítás',copy:'Adatok másolása',verify:'Ellenőrzés',flip:'Újratelepítés',redeploy:'Újratelepítés',cleanup:'Régi adatok törlése'};
|
||||
var s=names[job.phase]||job.phase;
|
||||
if(job.phase==='copy'&&job.bytes_total>0){ s+=' ('+Math.floor(100*job.bytes_done/job.bytes_total)+'% — '+appMigFmtGB(job.bytes_done)+'/'+appMigFmtGB(job.bytes_total)+')'; }
|
||||
return s+'…';
|
||||
}
|
||||
function appMigWatch(){
|
||||
var panel=document.getElementById('app-migrate-progress');
|
||||
if(panel) panel.style.display='block';
|
||||
function tick(){
|
||||
fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){
|
||||
var job=d.data&&d.data.job;
|
||||
if(!job){ if(panel) panel.textContent='Nincs folyamatban áthelyezés.'; return; }
|
||||
if(panel) panel.innerHTML=appMigRender(job);
|
||||
if(job.phase==='done'){ if(panel) panel.innerHTML+='<br><strong>Kész</strong>'; setTimeout(function(){location.reload();},1500); return; }
|
||||
if(job.phase==='aborted'){ if(panel) panel.innerHTML+='<br><strong style="color:var(--danger,#c0392b)">Megszakadt: '+(job.error||'')+'</strong><br>A régi adatok érintetlenek.'; return; }
|
||||
setTimeout(tick,1500);
|
||||
}).catch(function(){ setTimeout(tick,2000); });
|
||||
}
|
||||
tick();
|
||||
}
|
||||
function appMigrate(app,label){
|
||||
var sel=document.getElementById('app-migrate-target');
|
||||
var target=sel?sel.value:'';
|
||||
if(!target){ alert('Válassz céltárhelyet.'); return; }
|
||||
if(!confirm('Áthelyezed a(z) '+label+' adatait ide: '+target+'?\n\nAz alkalmazás rövid időre leáll. A régi adatok csak sikeres áthelyezés után törlődnek.')) return;
|
||||
fetch('/api/storage/migrate-app',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({app:app,target:target})})
|
||||
.then(function(r){return r.json();}).then(function(d){ if(d.ok){ appMigWatch(); } else { alert('Hiba: '+(d.error||'ismeretlen')); } })
|
||||
.catch(function(e){ alert('Hiba: '+e); });
|
||||
}
|
||||
(function(){ fetch('/api/storage/migrate/status').then(function(r){return r.json();}).then(function(d){ if(d.data&&d.data.job){ appMigWatch(); } }).catch(function(){}); })();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-grid">
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Mire használható?</h3>
|
||||
<ul class="app-info-list">
|
||||
<li>Jelszavak biztonságos tárolása és automatikus kitöltése</li><li>Bitwarden kliensek teljes kompatibilitása (böngésző, mobil, asztali)</li><li>Jelszavak megosztása családtagokkal szervezeten belül</li><li>Kétfaktoros hitelesítés (TOTP) kódok tárolása</li><li>Biztonságos jegyzetek és bankkártya adatok tárolása</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Első lépések</h3>
|
||||
<ol class="app-info-list">
|
||||
<li>Nyisd meg a vault.DOMAIN címet a böngészőben</li><li>Hozd létre a fiókodat erős mesterjelszóval</li><li>Telepítsd a Bitwarden bővítményt a böngésződbe</li><li>Telepítsd a Bitwarden alkalmazást a telefonodra</li><li>Importáld a meglévő jelszavaidat (Chrome, Firefox, LastPass, stb.)</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-info-card">
|
||||
<h3>Dokumentáció</h3>
|
||||
<p><a href="https://github.com/dani-garcia/vaultwarden/wiki" target="_blank" class="app-info-link">Hivatalos dokumentáció ↗</a></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+1903
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
404 page not found
|
||||
+618
@@ -0,0 +1,618 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title> — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name"></span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version"></span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div style="display:flex;align-items:center;gap:1rem">
|
||||
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
|
||||
<h2>Alkalmazás importálás</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card" style="max-width:700px">
|
||||
<p style="color:var(--text-3)">Nem található .fab csomag a regisztrált tárolókon.</p>
|
||||
<p style="color:var(--text-3);font-size:.85rem">Exportálj egy alkalmazást az alkalmazás oldaláról, vagy másolj egy .fab fájlt a <code>{tároló}/felhom-data/exports/</code> könyvtárba.</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="previewCard" class="card" style="max-width:700px;display:none">
|
||||
<h3 id="previewTitle">Csomag részletei</h3>
|
||||
|
||||
<div id="passwordPrompt" style="display:none;margin-bottom:1rem">
|
||||
<p>Ez a csomag jelszóval védett. Kérlek add meg a jelszót:</p>
|
||||
<div style="display:flex;gap:.5rem">
|
||||
<input type="password" id="importPassword" placeholder="Jelszó" style="flex:1;padding:.5rem">
|
||||
<button class="btn btn-primary" onclick="loadManifest()">Megnyitás</button>
|
||||
</div>
|
||||
<div id="passwordError" style="display:none;color:var(--crit);margin-top:.5rem"></div>
|
||||
</div>
|
||||
|
||||
<div id="manifestInfo" style="display:none">
|
||||
<div style="background:var(--bg-2);border-radius:8px;padding:1rem;margin-bottom:1rem">
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
||||
<span>Alkalmazás:</span>
|
||||
<strong id="mfName">-</strong>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
||||
<span>Exportálva:</span>
|
||||
<span id="mfDate">-</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
||||
<span>Controller verzió:</span>
|
||||
<span id="mfVersion">-</span>
|
||||
</div>
|
||||
<div id="mfDBRow" style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
||||
<span>Adatbázis:</span>
|
||||
<span id="mfDB">-</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
||||
<span>Adatok:</span>
|
||||
<span id="mfDataType">-</span>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between">
|
||||
<span>Méret:</span>
|
||||
<span id="mfSize">-</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="overwriteWarning" style="display:none;background:var(--warning-bg, #fff3cd);border:1px solid var(--warning-border, #ffc107);border-radius:8px;padding:1rem;margin-bottom:1rem">
|
||||
<strong>⚠ FIGYELEM:</strong> A meglévő <span id="overwriteAppName"></span> alkalmazás konfigurációja és összes adata felül lesz írva!
|
||||
</div>
|
||||
|
||||
<button id="importBtn" class="btn btn-primary" onclick="startImport()" style="width:100%">
|
||||
Visszaállítás indítása
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="progressCard" class="card" style="max-width:700px;display:none">
|
||||
<h3>Importálás folyamata</h3>
|
||||
<div id="progressSteps"></div>
|
||||
<div id="progressError" style="display:none;color:var(--crit);margin-top:1rem;font-weight:600"></div>
|
||||
</div>
|
||||
|
||||
<div id="doneCard" class="card" style="max-width:700px;display:none">
|
||||
<h3 style="color:var(--blue)">Importálás kész!</h3>
|
||||
<p>Az alkalmazás sikeresen visszaállítva.</p>
|
||||
<a id="doneLink" href="/stacks" class="btn btn-primary">Alkalmazások megtekintése</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var selectedPath = '';
|
||||
var selectedEncrypted = false;
|
||||
var selectedManifest = null;
|
||||
var pollTimer = null;
|
||||
|
||||
function csrfH() {
|
||||
var el = document.querySelector('meta[name="csrf-token"]');
|
||||
return el ? {'X-CSRF-Token': el.content, 'Content-Type': 'application/json'} : {'Content-Type': 'application/json'};
|
||||
}
|
||||
|
||||
function showPreview(path, encrypted) {
|
||||
selectedPath = path;
|
||||
selectedEncrypted = encrypted;
|
||||
selectedManifest = null;
|
||||
|
||||
document.getElementById('previewCard').style.display = 'block';
|
||||
document.getElementById('manifestInfo').style.display = 'none';
|
||||
document.getElementById('passwordPrompt').style.display = 'none';
|
||||
document.getElementById('passwordError').style.display = 'none';
|
||||
document.getElementById('progressCard').style.display = 'none';
|
||||
document.getElementById('doneCard').style.display = 'none';
|
||||
|
||||
if (encrypted) {
|
||||
document.getElementById('passwordPrompt').style.display = 'block';
|
||||
document.getElementById('importPassword').value = '';
|
||||
document.getElementById('importPassword').focus();
|
||||
} else {
|
||||
loadManifest();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadManifest() {
|
||||
var password = selectedEncrypted ? document.getElementById('importPassword').value : '';
|
||||
|
||||
try {
|
||||
var resp = await fetch('/api/export/manifest', {
|
||||
method: 'POST',
|
||||
headers: csrfH(),
|
||||
body: JSON.stringify({path: selectedPath, password: password})
|
||||
});
|
||||
var data = await resp.json();
|
||||
|
||||
if (!data.ok) {
|
||||
if (selectedEncrypted) {
|
||||
document.getElementById('passwordError').textContent = data.error || 'Hibás jelszó';
|
||||
document.getElementById('passwordError').style.display = 'block';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.needs_password) {
|
||||
document.getElementById('passwordPrompt').style.display = 'block';
|
||||
document.getElementById('importPassword').focus();
|
||||
return;
|
||||
}
|
||||
|
||||
selectedManifest = data.manifest;
|
||||
showManifest(data.manifest);
|
||||
} catch(e) {
|
||||
console.error('Manifest error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
function showManifest(m) {
|
||||
document.getElementById('passwordPrompt').style.display = 'none';
|
||||
document.getElementById('manifestInfo').style.display = 'block';
|
||||
|
||||
document.getElementById('previewTitle').textContent = (m.display_name || m.app_name) + ' — Csomag részletei';
|
||||
document.getElementById('mfName').textContent = m.display_name || m.app_name;
|
||||
document.getElementById('mfDate').textContent = m.exported_at ? new Date(m.exported_at).toLocaleString('hu-HU') : '-';
|
||||
document.getElementById('mfVersion').textContent = m.controller_version || '-';
|
||||
|
||||
if (m.has_database && m.db_type) {
|
||||
document.getElementById('mfDB').textContent = m.db_type;
|
||||
document.getElementById('mfDBRow').style.display = 'flex';
|
||||
} else {
|
||||
document.getElementById('mfDBRow').style.display = 'none';
|
||||
}
|
||||
|
||||
var dataType = [];
|
||||
if (m.has_hdd_data) dataType.push('HDD');
|
||||
if (m.has_volume_data) dataType.push('Docker volume');
|
||||
document.getElementById('mfDataType').textContent = dataType.length ? dataType.join(', ') : 'Nincs';
|
||||
|
||||
|
||||
var bytes = m.total_size_bytes || 0;
|
||||
var sizeStr = bytes > 1073741824 ? (bytes / 1073741824).toFixed(1) + ' GB' :
|
||||
bytes > 1048576 ? (bytes / 1048576).toFixed(1) + ' MB' :
|
||||
bytes > 1024 ? (bytes / 1024).toFixed(1) + ' KB' : bytes + ' B';
|
||||
document.getElementById('mfSize').textContent = sizeStr;
|
||||
|
||||
|
||||
|
||||
var warn = document.getElementById('overwriteWarning');
|
||||
|
||||
document.getElementById('overwriteAppName').textContent = m.display_name || m.app_name;
|
||||
warn.style.display = 'block';
|
||||
}
|
||||
|
||||
async function startImport() {
|
||||
document.getElementById('importBtn').disabled = true;
|
||||
document.getElementById('progressCard').style.display = 'block';
|
||||
document.getElementById('doneCard').style.display = 'none';
|
||||
|
||||
var password = selectedEncrypted ? document.getElementById('importPassword').value : '';
|
||||
|
||||
try {
|
||||
var resp = await fetch('/api/export/import', {
|
||||
method: 'POST',
|
||||
headers: csrfH(),
|
||||
body: JSON.stringify({path: selectedPath, password: password})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showError(data.error || 'Hiba történt');
|
||||
return;
|
||||
}
|
||||
pollStatus();
|
||||
} catch(e) {
|
||||
showError(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
function pollStatus() {
|
||||
if (pollTimer) clearInterval(pollTimer);
|
||||
pollTimer = setInterval(async function() {
|
||||
try {
|
||||
var resp = await fetch('/api/export/import/status');
|
||||
var data = await resp.json();
|
||||
renderSteps(data.steps || []);
|
||||
|
||||
if (data.error) {
|
||||
showError(data.error);
|
||||
clearInterval(pollTimer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.done && !data.error) {
|
||||
clearInterval(pollTimer);
|
||||
document.getElementById('progressCard').style.display = 'none';
|
||||
document.getElementById('doneCard').style.display = 'block';
|
||||
if (data.stack_name) {
|
||||
document.getElementById('doneLink').href = '/stacks/' + data.stack_name + '/deploy';
|
||||
document.getElementById('doneLink').textContent = 'Alkalmazás megtekintése';
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Poll error:', e);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function renderSteps(steps) {
|
||||
var html = '';
|
||||
for (var i = 0; i < steps.length; i++) {
|
||||
var s = steps[i];
|
||||
var icon = '○';
|
||||
if (s.status === 'running') icon = '⟳';
|
||||
if (s.status === 'done') icon = '✓';
|
||||
if (s.status === 'failed') icon = '✗';
|
||||
var cls = s.status === 'failed' ? 'color:var(--crit)' : s.status === 'done' ? 'color:var(--blue)' : s.status === 'running' ? 'color:var(--blue)' : '';
|
||||
html += '<div style="padding:.25rem 0;' + cls + '">' + icon + ' ' + s.label;
|
||||
if (s.error) html += ' <span style="font-size:.85rem">(' + s.error + ')</span>';
|
||||
html += '</div>';
|
||||
}
|
||||
document.getElementById('progressSteps').innerHTML = html;
|
||||
}
|
||||
|
||||
function showError(msg) {
|
||||
var el = document.getElementById('progressError');
|
||||
el.textContent = msg;
|
||||
el.style.display = 'block';
|
||||
document.getElementById('importBtn').disabled = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+1232
File diff suppressed because it is too large
Load Diff
+650
@@ -0,0 +1,650 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Beállítások — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name">Demo Ügyfél</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class="active"><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.102.0</span>
|
||||
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
<div class="alerts-container">
|
||||
|
||||
|
||||
<div class="alert-banner alert-banner-error">
|
||||
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
|
||||
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
|
||||
<a href="/settings" class="alert-link">Beállítások →</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Beállítások — Rendszer</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Rendszer konfiguráció</h3>
|
||||
<p class="settings-card-desc">Az üzemeltető által beállított értékek. Módosításhoz kérd az üzemeltetőt.</p>
|
||||
<div class="settings-grid">
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Ügyfél azonosító</span>
|
||||
<span class="settings-value mono">demo-felhom</span>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Ügyfél neve</span>
|
||||
<span class="settings-value">Demo Ügyfél</span>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Domain</span>
|
||||
<span class="settings-value mono">demo-felhom.eu</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Alkalmazás sablon forrás</span>
|
||||
<span class="settings-value mono settings-value-truncate">https://gitea.dooplex.hu/admin/app-catalog-felhom.eu.git</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Sablon szinkronizálás</span>
|
||||
<span class="settings-value mono">15m</span>
|
||||
</div>
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Biztonsági mentés</span>
|
||||
<span class="settings-value"><span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span></span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Mentés ütemezés</span>
|
||||
<span class="settings-value mono">02:30 / 03:00</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Monitoring</span>
|
||||
<span class="settings-value"><span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span></span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Healthchecks URL</span>
|
||||
<span class="settings-value mono settings-value-truncate">https://status.felhom.eu</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Hub jelentés</span>
|
||||
<span class="settings-value"><span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Aktív</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Verzió és frissítés</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Jelenlegi verzió</span>
|
||||
<span class="settings-value mono">0.102.0</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Utolsó ellenőrzés</span>
|
||||
<span class="settings-value mono">2026-07-06T22:33:00Z</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Hiba</span>
|
||||
<span class="settings-value state-text-crit">Registry lekérdezés sikertelen: registry hitelesítő adatok hiányoznak</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Automatikus frissítés</span>
|
||||
<span class="settings-value">
|
||||
–
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Minimális verzió (üzemeltető)</span>
|
||||
<span class="settings-value mono">
|
||||
0.87.0
|
||||
<span style="margin-left:0.5em; color:#888;">— a rendszer automatikusan erre a verzióra frissít, ha régebbi</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Utolsó frissítés</span>
|
||||
<span class="settings-value">
|
||||
<span class="state-text-run"><svg class="ico ico-sm"><use href="#i-check"/></svg> Sikeres</span> (0.86.0 → 0.87.0)
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-row" style="padding-top: 0.5em;">
|
||||
<span class="settings-label"></span>
|
||||
<span class="settings-value">
|
||||
<button class="btn btn-secondary btn-sm" id="btn-check-update" onclick="checkUpdate()">Frissítés keresése</button>
|
||||
|
||||
<span id="update-status-msg" style="margin-left:0.5em; display:none;"></span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="dialog-root"></div>
|
||||
<script>
|
||||
|
||||
function escText(s){var d=document.createElement('div');d.textContent=String(s==null?'':s);return d.innerHTML;}
|
||||
function closeDialog(){ var r=document.getElementById('dialog-root'); if(r) r.innerHTML=''; }
|
||||
function openDialog(opts){
|
||||
var root=document.getElementById('dialog-root');
|
||||
if(!root) return;
|
||||
root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
|
||||
+'<h3>'+escText(opts.title||'Megerősítés')+'</h3>'
|
||||
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>'
|
||||
+'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'Megerősítés')+'</button>'
|
||||
+'<button type="button" class="btn btn-outline" id="dialog-cancel">Mégsem</button></div>'
|
||||
+'</div></div>';
|
||||
document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
|
||||
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
|
||||
}
|
||||
|
||||
function checkUpdate() {
|
||||
var btn = document.getElementById('btn-check-update');
|
||||
var msg = document.getElementById('update-status-msg');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Ellenőrzés...';
|
||||
msg.style.display = 'none';
|
||||
fetch('/api/selfupdate/check', {method:'POST', headers: csrfHeaders()})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
msg.textContent = data.error || 'Hiba történt';
|
||||
msg.style.display = 'inline';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Frissítés keresése';
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
msg.textContent = 'Kapcsolódási hiba';
|
||||
msg.style.display = 'inline';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Frissítés keresése';
|
||||
});
|
||||
}
|
||||
|
||||
function triggerUpdate() {
|
||||
openDialog({title:'Vezérlő frissítése', confirmLabel:'Frissítés',
|
||||
message:'Biztosan frissíti a controllert?\n\nA folyamat alatt a vezérlőpult rövid időre elérhetetlenné válik.',
|
||||
onConfirm:doTriggerUpdate});
|
||||
}
|
||||
function doTriggerUpdate() {
|
||||
var btn = document.getElementById('btn-trigger-update');
|
||||
var checkBtn = document.getElementById('btn-check-update');
|
||||
var msg = document.getElementById('update-status-msg');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Frissítés...';
|
||||
if (checkBtn) checkBtn.disabled = true;
|
||||
msg.textContent = 'Frissítés folyamatban...';
|
||||
msg.style.display = 'inline';
|
||||
fetch('/api/selfupdate/update', {method:'POST', headers: csrfHeaders()})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
if (data.ok) {
|
||||
msg.textContent = 'Újraindulás...';
|
||||
pollUntilBack();
|
||||
} else {
|
||||
msg.textContent = data.error || 'Hiba történt';
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Frissítés telepítése';
|
||||
if (checkBtn) checkBtn.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(function() {
|
||||
msg.textContent = 'Kapcsolódási hiba';
|
||||
pollUntilBack();
|
||||
});
|
||||
}
|
||||
|
||||
function pollUntilBack() {
|
||||
var iv = setInterval(function() {
|
||||
fetch('/api/health')
|
||||
.then(function(r) {
|
||||
if (r.ok) {
|
||||
clearInterval(iv);
|
||||
location.reload();
|
||||
}
|
||||
})
|
||||
.catch(function() {});
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Vezérlő újraindítása</h3>
|
||||
<p class="settings-card-desc">
|
||||
Ha a vezérlő hibásan működik, itt biztonságosan újraindíthatja — nem kell az egész szervert újraindítani.
|
||||
Az alkalmazásai futnak tovább; csak a vezérlő indul újra (néhány másodperc).
|
||||
</p>
|
||||
<div id="restart-status"></div>
|
||||
<button type="button" class="btn btn-outline" id="btn-restart-controller" onclick="restartController()">Vezérlő újraindítása</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Kiszolgáló újraindítása</h3>
|
||||
<p class="settings-card-desc">
|
||||
Az egész kiszolgáló (szerver) újraindítása. Minden alkalmazás rövid időre leáll, és a vezérlőpult kb. 30 másodpercig nem elérhető. Akkor használja, ha a teljes rendszer újraindítására van szükség — egyébként a fenti „Vezérlő újraindítása” elegendő.
|
||||
</p>
|
||||
<div id="server-restart-status"></div>
|
||||
<button type="button" class="btn btn-outline" id="btn-restart-server" onclick="restartServer()">Kiszolgáló újraindítása</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function restartController() {
|
||||
openDialog({title:'Vezérlő újraindítása', confirmLabel:'Újraindítás',
|
||||
message:'Biztosan újraindítja a vezérlőt? A művelet néhány másodpercig tart, és a felület rövid időre elérhetetlen lesz.',
|
||||
onConfirm:doRestartController});
|
||||
}
|
||||
function doRestartController() {
|
||||
var btn = document.getElementById('btn-restart-controller');
|
||||
var status = document.getElementById('restart-status');
|
||||
btn.disabled = true;
|
||||
status.innerHTML = '<div class="alert alert-info">Újraindítás folyamatban… újracsatlakozás…</div>';
|
||||
fetch('/api/selfrestart', { method: 'POST', headers: csrfHeaders() })
|
||||
.then(function(){ pollRestart(0); })
|
||||
.catch(function(){ pollRestart(0); });
|
||||
}
|
||||
function restartServer() {
|
||||
openDialog({title:'Kiszolgáló újraindítása', confirmLabel:'Újraindítás',
|
||||
message:'Biztosan újraindítja a kiszolgálót? Az alkalmazások és a vezérlőpult kb. 30 másodpercre elérhetetlenné válnak.',
|
||||
onConfirm:doRestartServer});
|
||||
}
|
||||
function doRestartServer() {
|
||||
var btn = document.getElementById('btn-restart-server');
|
||||
var status = document.getElementById('server-restart-status');
|
||||
if (btn) btn.disabled = true;
|
||||
if (status) status.innerHTML = '<div class="alert alert-info">Újraindítás folyamatban… a vezérlőpult néhány másodperc múlva újratölt.</div>';
|
||||
fetch('/api/server/reboot', { method: 'POST', headers: csrfHeaders() })
|
||||
.then(function(){ pollRestart(0); })
|
||||
.catch(function(){ pollRestart(0); });
|
||||
}
|
||||
function pollRestart(attempt) {
|
||||
if (attempt > 60) {
|
||||
document.getElementById('restart-status').innerHTML =
|
||||
'<div class="alert alert-error">Az újraindítás a vártnál tovább tart. Töltse újra az oldalt kézzel.</div>';
|
||||
return;
|
||||
}
|
||||
setTimeout(function(){
|
||||
fetch('/', { method: 'GET', cache: 'no-store' })
|
||||
.then(function(r){ if (r.ok) { window.location.reload(); } else { pollRestart(attempt + 1); } })
|
||||
.catch(function(){ pollRestart(attempt + 1); });
|
||||
}, 2000);
|
||||
}
|
||||
</script>
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+498
@@ -0,0 +1,498 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Értesítések — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name">Demo Ügyfél</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class="active"><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.102.0</span>
|
||||
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
<div class="alerts-container">
|
||||
|
||||
|
||||
<div class="alert-banner alert-banner-error">
|
||||
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
|
||||
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
|
||||
<a href="/settings" class="alert-link">Beállítások →</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Beállítások — Értesítések</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Értesítések</h3>
|
||||
|
||||
|
||||
|
||||
<form method="POST" action="/settings/notifications">
|
||||
<input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<div class="form-group">
|
||||
<label for="notification_email">E-mail cím</label>
|
||||
<input type="email" id="notification_email" name="notification_email"
|
||||
value=""
|
||||
placeholder="pelda@email.hu" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Hibák és figyelmeztetések:</label>
|
||||
<div class="checkbox-group">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_backup_failed" checked>
|
||||
<span class="toggle-label">Biztonsági mentés sikertelen</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_db_dump_failed" checked>
|
||||
<span class="toggle-label">Adatbázis mentés sikertelen</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_backup_integrity_failed" >
|
||||
<span class="toggle-label">Mentés sérülés észlelve</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_crossdrive_failed" >
|
||||
<span class="toggle-label">Másodlagos mentés sikertelen</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_disk_alerts" checked>
|
||||
<span class="toggle-label">Lemez figyelmeztetés (90%+)</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_storage_disconnected" checked>
|
||||
<span class="toggle-label">Meghajtó leválasztva</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_node_down" checked>
|
||||
<span class="toggle-label">Szerver nem elérhető</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_health_critical" checked>
|
||||
<span class="toggle-label">Rendszer állapot kritikus</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_expected_missed" checked>
|
||||
<span class="toggle-label">Elvárt mentés elmaradt</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Tájékoztató:</label>
|
||||
<div class="checkbox-group">
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_storage_reconnected" >
|
||||
<span class="toggle-label">Meghajtó újra csatlakoztatva</span>
|
||||
</label>
|
||||
<label class="toggle">
|
||||
<input type="checkbox" name="event_health_recovered" >
|
||||
<span class="toggle-label">Rendszer állapot helyreállt</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cooldown_hours">Értesítési szünet</label>
|
||||
<div class="form-inline">
|
||||
<input type="number" id="cooldown_hours" name="cooldown_hours" min="1" max="168"
|
||||
value="6"
|
||||
class="form-control form-control-narrow">
|
||||
<span class="form-hint">óra (azonos probléma esetén ennyi ideig nem küld újat)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Mentés</button>
|
||||
<button type="submit" formaction="/settings/notifications/test" class="btn btn-outline">Teszt email küldése</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Alkalmazás-email</h3>
|
||||
<p class="settings-card-desc">
|
||||
Az alkalmazások a Felhom-on keresztül küldhetnek emailt (pl. jelszó-visszaállítás, meghívók),
|
||||
külön email-szolgáltató beállítása nélkül. A feladó címe minden alkalmazásnál a saját
|
||||
<em><alkalmazás>@felhom.eu</em> címe lesz.
|
||||
</p>
|
||||
|
||||
|
||||
<form method="POST" action="/settings/app-email">
|
||||
<input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<div class="form-group">
|
||||
<label style="display:flex;align-items:center;gap:.5rem">
|
||||
<input type="checkbox" name="app_email_enabled" value="on" checked>
|
||||
Alkalmazás-email engedélyezése
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="app_email_from_name">Feladó neve (opcionális)</label>
|
||||
<input type="text" id="app_email_from_name" name="app_email_from_name"
|
||||
value="Felhom Demo" placeholder="pl. a háztartás neve" class="form-control">
|
||||
<span class="form-hint">Ez jelenik meg a kimenő emailek feladójaként az alkalmazás neve mellett.</span>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Mentés</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="settings-card-desc" style="margin-top:.75rem">
|
||||
Bekapcsolás után az egyes alkalmazásoknál is engedélyezni kell az email-küldést (az alkalmazás oldalán).
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+793
@@ -0,0 +1,793 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Biztonság és hozzáférés — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name">Demo Ügyfél</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class=""><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class="active"><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.102.0</span>
|
||||
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
<div class="alerts-container">
|
||||
|
||||
|
||||
<div class="alert-banner alert-banner-error">
|
||||
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
|
||||
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
|
||||
<a href="/settings" class="alert-link">Beállítások →</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Beállítások — Biztonság és hozzáférés</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Jelszó módosítás</h3>
|
||||
|
||||
|
||||
<form method="POST" action="/settings/password">
|
||||
<input type="hidden" name="_csrf" value="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<div class="form-group">
|
||||
<label for="current_password">Jelenlegi jelszó</label>
|
||||
<input type="password" id="current_password" name="current_password" required
|
||||
placeholder="Adja meg a jelenlegi jelszavát" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_password">Új jelszó</label>
|
||||
<input type="password" id="new_password" name="new_password" required minlength="8"
|
||||
placeholder="Legalább 8 karakter" class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm_password">Új jelszó megerősítése</label>
|
||||
<input type="password" id="confirm_password" name="confirm_password" required minlength="8"
|
||||
placeholder="Jelszó mégegyszer" class="form-control">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Jelszó módosítása</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-card">
|
||||
<h3>Földrajzi korlátozás</h3>
|
||||
<p class="settings-card-desc">
|
||||
Ország alapján korlátozható a webes alkalmazások elérése a Cloudflare WAF segítségével.
|
||||
<br><span class="form-hint">A helyi hálózati hozzáférés mindig engedélyezett (nem halad át a Cloudflare-en).</span>
|
||||
</p>
|
||||
|
||||
|
||||
<div id="geo-status-msg"></div>
|
||||
|
||||
<label class="toggle" style="margin-bottom:1rem">
|
||||
<input type="checkbox" id="geo-enabled"
|
||||
onchange="toggleGeo(this.checked)">
|
||||
<span class="toggle-label">Geo-korlátozás aktív</span>
|
||||
</label>
|
||||
|
||||
<div id="geo-details" style="display:none">
|
||||
|
||||
<div class="form-group">
|
||||
<label>Engedélyezett országok (globális)</label>
|
||||
<div class="geo-country-selector" id="geo-countries">
|
||||
<input type="text" id="geo-search" class="form-control"
|
||||
placeholder="Ország keresése..."
|
||||
autocomplete="off"
|
||||
oninput="filterCountries(this.value)"
|
||||
onfocus="showCountryList()"
|
||||
onblur="setTimeout(function(){hideCountryList()},200)">
|
||||
<div class="geo-country-list" id="geo-country-list"></div>
|
||||
</div>
|
||||
<div class="geo-selected-tags" id="geo-selected-tags"></div>
|
||||
<span class="form-hint">Csak a kiválasztott országokból érhető el a rendszer.</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top:1.5rem">
|
||||
<label>Alkalmazás-specifikus felülírások</label>
|
||||
<div id="geo-app-overrides"></div>
|
||||
|
||||
<div style="margin-top:.5rem;display:flex;align-items:center;gap:.5rem">
|
||||
<select id="geo-add-app-select" class="form-control" style="max-width:250px">
|
||||
<option value="">— Alkalmazás kiválasztása —</option>
|
||||
|
||||
<option value="actualbudget">ActualBudget</option>
|
||||
|
||||
<option value="bookstack">BookStack</option>
|
||||
|
||||
<option value="calcom">Cal.com</option>
|
||||
|
||||
<option value="gitea">Gitea</option>
|
||||
|
||||
<option value="mealie">Mealie</option>
|
||||
|
||||
<option value="navidrome">Navidrome</option>
|
||||
|
||||
<option value="nextcloud">Nextcloud</option>
|
||||
|
||||
<option value="paperless-ngx">Paperless-ngx</option>
|
||||
|
||||
<option value="rallly">Rallly</option>
|
||||
|
||||
<option value="sparkyfitness">SparkyFitness</option>
|
||||
|
||||
<option value="uptime-kuma">Uptime Kuma</option>
|
||||
|
||||
<option value="vaultwarden">Vaultwarden</option>
|
||||
|
||||
</select>
|
||||
<button class="btn btn-sm btn-outline" onclick="addAppOverride()">+ Hozzáadás</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group" style="margin-top:1.5rem">
|
||||
<div style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
|
||||
<button class="btn btn-primary" id="btn-geo-save" onclick="saveGeoSettings()">
|
||||
Mentés és szinkronizálás
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline" onclick="triggerGeoSync()">Kézi szinkronizálás</button>
|
||||
<span id="geo-sync-status" class="form-hint">
|
||||
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="dialog-root"></div>
|
||||
<script>
|
||||
|
||||
function escText(s){var d=document.createElement('div');d.textContent=String(s==null?'':s);return d.innerHTML;}
|
||||
function closeDialog(){ var r=document.getElementById('dialog-root'); if(r) r.innerHTML=''; }
|
||||
function openDialog(opts){
|
||||
var root=document.getElementById('dialog-root');
|
||||
if(!root) return;
|
||||
root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
|
||||
+'<h3>'+escText(opts.title||'Megerősítés')+'</h3>'
|
||||
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>'
|
||||
+'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'Megerősítés')+'</button>'
|
||||
+'<button type="button" class="btn btn-outline" id="dialog-cancel">Mégsem</button></div>'
|
||||
+'</div></div>';
|
||||
document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
|
||||
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
|
||||
}
|
||||
|
||||
(function(){
|
||||
|
||||
var allCountries = [];
|
||||
var selectedCountries = ["HU"];
|
||||
var appOverrides = {};
|
||||
|
||||
|
||||
function ensureCountries(cb) {
|
||||
if (allCountries.length > 0) { cb(); return; }
|
||||
fetch('/api/geo/countries', {headers: csrfHeaders()})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if (d.ok) allCountries = d.data;
|
||||
cb();
|
||||
})
|
||||
.catch(function(){ cb(); });
|
||||
}
|
||||
|
||||
window.toggleGeo = function(enabled) {
|
||||
document.getElementById('geo-details').style.display = enabled ? '' : 'none';
|
||||
if (enabled) ensureCountries(renderTags);
|
||||
};
|
||||
|
||||
window.showCountryList = function() {
|
||||
ensureCountries(function(){ filterCountries(document.getElementById('geo-search').value); });
|
||||
};
|
||||
|
||||
window.hideCountryList = function() {
|
||||
document.getElementById('geo-country-list').style.display = 'none';
|
||||
};
|
||||
|
||||
window.filterCountries = function(query) {
|
||||
var list = document.getElementById('geo-country-list');
|
||||
var q = query.toLowerCase();
|
||||
var html = '';
|
||||
var count = 0;
|
||||
for (var i = 0; i < allCountries.length && count < 15; i++) {
|
||||
var c = allCountries[i];
|
||||
if (selectedCountries.indexOf(c.code) >= 0) continue;
|
||||
if (q && c.name.toLowerCase().indexOf(q) < 0 && c.code.toLowerCase().indexOf(q) < 0) continue;
|
||||
html += '<div class="geo-country-option" onmousedown="addCountry(\'' + c.code + '\',\'' + escHtml(c.name) + '\')">'
|
||||
+ escHtml(c.name) + ' <small>(' + c.code + ')</small></div>';
|
||||
count++;
|
||||
}
|
||||
list.innerHTML = html || '<div class="geo-country-option" style="opacity:.5">Nincs találat</div>';
|
||||
|
||||
|
||||
|
||||
list.style.display = count > 0 || q ? 'block' : 'none';
|
||||
};
|
||||
|
||||
window.addCountry = function(code, name) {
|
||||
if (selectedCountries.indexOf(code) >= 0) return;
|
||||
selectedCountries.push(code);
|
||||
renderTags();
|
||||
document.getElementById('geo-search').value = '';
|
||||
hideCountryList();
|
||||
};
|
||||
|
||||
window.removeCountry = function(code) {
|
||||
if (code === 'HU') {
|
||||
openDialog({title:'Figyelem', confirmLabel:'Eltávolítás',
|
||||
message:'Figyelem: Magyarország eltávolítása azt jelenti, hogy magyar IP-ről sem lesz elérhető a rendszer távolról. Biztosan folytatja?',
|
||||
onConfirm:function(){ doRemoveCountry(code); }});
|
||||
return;
|
||||
}
|
||||
doRemoveCountry(code);
|
||||
};
|
||||
function doRemoveCountry(code) {
|
||||
selectedCountries = selectedCountries.filter(function(c){return c !== code});
|
||||
renderTags();
|
||||
}
|
||||
|
||||
function renderTags() {
|
||||
var el = document.getElementById('geo-selected-tags');
|
||||
var html = '';
|
||||
for (var i = 0; i < selectedCountries.length; i++) {
|
||||
var code = selectedCountries[i];
|
||||
var name = countryName(code);
|
||||
var isHU = code === 'HU' ? ' geo-tag-hu' : '';
|
||||
html += '<span class="geo-tag' + isHU + '">'
|
||||
+ escHtml(name) + ' (' + code + ') '
|
||||
+ '<span class="geo-tag-remove" onclick="removeCountry(\'' + code + '\')">×</span>'
|
||||
+ '</span>';
|
||||
}
|
||||
el.innerHTML = html;
|
||||
renderAppOverrides();
|
||||
}
|
||||
|
||||
function countryName(code) {
|
||||
for (var i = 0; i < allCountries.length; i++) {
|
||||
if (allCountries[i].code === code) return allCountries[i].name;
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
|
||||
window.addAppOverride = function() {
|
||||
var sel = document.getElementById('geo-add-app-select');
|
||||
var appName = sel.value;
|
||||
if (!appName) return;
|
||||
if (!appOverrides) appOverrides = {};
|
||||
if (appOverrides[appName]) { sel.value = ''; return; }
|
||||
|
||||
appOverrides[appName] = {allowed_countries: selectedCountries.slice()};
|
||||
sel.value = '';
|
||||
renderAppOverrides();
|
||||
};
|
||||
|
||||
window.removeAppOverride = function(appName) {
|
||||
delete appOverrides[appName];
|
||||
renderAppOverrides();
|
||||
};
|
||||
|
||||
window.toggleAppCountry = function(appName, code, el) {
|
||||
var ov = appOverrides[appName];
|
||||
if (!ov) return;
|
||||
var idx = ov.allowed_countries.indexOf(code);
|
||||
if (idx >= 0) {
|
||||
if (code === 'HU') {
|
||||
openDialog({title:'Figyelem', confirmLabel:'Eltávolítás',
|
||||
message:'Magyarország eltávolítása nem ajánlott. Folytatja?',
|
||||
onConfirm:function(){ ov.allowed_countries.splice(ov.allowed_countries.indexOf(code), 1); },
|
||||
onCancel:function(){ el.checked = true; }});
|
||||
return;
|
||||
}
|
||||
ov.allowed_countries.splice(idx, 1);
|
||||
} else {
|
||||
ov.allowed_countries.push(code);
|
||||
}
|
||||
};
|
||||
|
||||
function renderAppOverrides() {
|
||||
var el = document.getElementById('geo-app-overrides');
|
||||
if (!appOverrides || Object.keys(appOverrides).length === 0) {
|
||||
el.innerHTML = '<p class="form-hint">Nincs alkalmazás-specifikus beállítás. Minden alkalmazás a globális beállítást követi.</p>';
|
||||
return;
|
||||
}
|
||||
var html = '';
|
||||
for (var appName in appOverrides) {
|
||||
var ov = appOverrides[appName];
|
||||
var displayName = appName;
|
||||
|
||||
var opts = document.getElementById('geo-add-app-select');
|
||||
if (opts) {
|
||||
for (var j = 0; j < opts.options.length; j++) {
|
||||
if (opts.options[j].value === appName) { displayName = opts.options[j].text; break; }
|
||||
}
|
||||
}
|
||||
html += '<div class="geo-app-override-row">';
|
||||
html += '<strong>' + escHtml(displayName) + '</strong>';
|
||||
html += '<div class="geo-selected-tags" style="flex:1;margin:0 .5rem">';
|
||||
for (var i = 0; i < ov.allowed_countries.length; i++) {
|
||||
var code = ov.allowed_countries[i];
|
||||
html += '<span class="geo-tag geo-tag-sm">' + code + '</span>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '<button class="btn btn-sm btn-outline" onclick="editAppOverride(\'' + appName + '\')">Szerkesztés</button>';
|
||||
html += '<button class="btn btn-sm btn-danger-outline" onclick="removeAppOverride(\'' + appName + '\')">Törlés</button>';
|
||||
html += '</div>';
|
||||
}
|
||||
el.innerHTML = html;
|
||||
}
|
||||
|
||||
window.editAppOverride = function(appName) {
|
||||
var ov = appOverrides[appName];
|
||||
if (!ov) return;
|
||||
ensureCountries(function(){
|
||||
var checked = {};
|
||||
for (var i = 0; i < ov.allowed_countries.length; i++) checked[ov.allowed_countries[i]] = true;
|
||||
var html = '<div class="geo-edit-overlay" id="geo-edit-' + appName + '">';
|
||||
html += '<h4>Engedélyezett országok: ' + escHtml(appName) + '</h4>';
|
||||
html += '<div class="geo-edit-grid">';
|
||||
for (var i = 0; i < allCountries.length; i++) {
|
||||
var c = allCountries[i];
|
||||
html += '<label class="geo-edit-item"><input type="checkbox" value="' + c.code + '"'
|
||||
+ (checked[c.code] ? ' checked' : '') + ' onchange="toggleAppCountry(\'' + appName + '\',\'' + c.code + '\',this)">'
|
||||
+ ' ' + escHtml(c.name) + ' (' + c.code + ')</label>';
|
||||
}
|
||||
html += '</div>';
|
||||
html += '<button class="btn btn-sm btn-primary" style="margin-top:.5rem" onclick="closeAppEdit(\'' + appName + '\')">Kész</button>';
|
||||
html += '</div>';
|
||||
document.getElementById('geo-app-overrides').innerHTML += html;
|
||||
});
|
||||
};
|
||||
|
||||
window.closeAppEdit = function(appName) {
|
||||
var el = document.getElementById('geo-edit-' + appName);
|
||||
if (el) el.remove();
|
||||
renderAppOverrides();
|
||||
};
|
||||
|
||||
|
||||
window.saveGeoSettings = function() {
|
||||
var btn = document.getElementById('btn-geo-save');
|
||||
var status = document.getElementById('geo-status-msg');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Mentés...';
|
||||
|
||||
var payload = {
|
||||
enabled: document.getElementById('geo-enabled').checked,
|
||||
allowed_countries: selectedCountries
|
||||
};
|
||||
|
||||
fetch('/api/geo/settings', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify(payload)
|
||||
})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if (d.ok) {
|
||||
status.innerHTML = '<div class="alert alert-info">' + (d.message || 'Mentve') + '</div>';
|
||||
|
||||
if (appOverrides && Object.keys(appOverrides).length > 0) {
|
||||
saveAllAppOverrides();
|
||||
}
|
||||
} else {
|
||||
status.innerHTML = '<div class="alert alert-error">' + (d.error || 'Hiba') + '</div>';
|
||||
}
|
||||
})
|
||||
.catch(function(err){
|
||||
status.innerHTML = '<div class="alert alert-error">Hálózati hiba</div>';
|
||||
})
|
||||
.finally(function(){
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Mentés és szinkronizálás';
|
||||
setTimeout(function(){ status.innerHTML = ''; }, 8000);
|
||||
});
|
||||
};
|
||||
|
||||
function saveAllAppOverrides() {
|
||||
for (var appName in appOverrides) {
|
||||
(function(name, ov){
|
||||
fetch('/api/stacks/' + name + '/geo/override', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({allowed_countries: ov.allowed_countries})
|
||||
});
|
||||
})(appName, appOverrides[appName]);
|
||||
}
|
||||
}
|
||||
|
||||
window.triggerGeoSync = function() {
|
||||
fetch('/api/geo/sync', {method:'POST', headers: csrfHeaders()})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
var status = document.getElementById('geo-sync-status');
|
||||
status.textContent = d.ok ? 'Szinkronizálás elindítva...' : (d.error || 'Hiba');
|
||||
setTimeout(function(){
|
||||
fetch('/api/geo/status', {headers: csrfHeaders()})
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if (d.ok && d.data) {
|
||||
var sync = d.data.last_sync || '';
|
||||
var err = d.data.last_sync_error || '';
|
||||
status.innerHTML = sync ? ('Utolsó: ' + sync.substring(0,19).replace('T',' ')) : '';
|
||||
if (err) status.innerHTML += ' <span class="state-text-crit">' + escHtml(err) + '</span>';
|
||||
}
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
|
||||
function escHtml(s) {
|
||||
var d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
|
||||
if (document.getElementById('geo-enabled') && document.getElementById('geo-enabled').checked) {
|
||||
ensureCountries(renderTags);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+2956
File diff suppressed because it is too large
Load Diff
+1104
File diff suppressed because it is too large
Load Diff
+477
@@ -0,0 +1,477 @@
|
||||
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="hu">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hálózati tárhely — Felhom.eu</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg">
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<meta name="csrf-token" content="ee8f0976099202660a6865daf2fe0e15217659da12a012a5dfd9233ce4cb157c">
|
||||
<script>
|
||||
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-2);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none" aria-hidden="true">
|
||||
<symbol id="i-hard-drive" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 16h.01" /> <path d="M2.212 11.577a2 2 0 0 0-.212.896V18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-5.527a2 2 0 0 0-.212-.896L18.55 5.11A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" /> <path d="M21.946 12.013H2.054" /> <path d="M6 16h.01" /></symbol>
|
||||
<symbol id="i-cpu" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20v2" /> <path d="M12 2v2" /> <path d="M17 20v2" /> <path d="M17 2v2" /> <path d="M2 12h2" /> <path d="M2 17h2" /> <path d="M2 7h2" /> <path d="M20 12h2" /> <path d="M20 17h2" /> <path d="M20 7h2" /> <path d="M7 20v2" /> <path d="M7 2v2" /> <rect x="4" y="4" width="16" height="16" rx="2" /> <rect x="8" y="8" width="8" height="8" rx="1" /></symbol>
|
||||
<symbol id="i-memory-stick" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 12v-2" /> <path d="M12 18v-2" /> <path d="M16 12v-2" /> <path d="M16 18v-2" /> <path d="M2 11h1.5" /> <path d="M20 18v-2" /> <path d="M20.5 11H22" /> <path d="M4 18v-2" /> <path d="M8 12v-2" /> <path d="M8 18v-2" /> <rect x="2" y="6" width="20" height="10" rx="2" /></symbol>
|
||||
<symbol id="i-thermometer" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" /></symbol>
|
||||
<symbol id="i-triangle-alert" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" /> <path d="M12 9v4" /> <path d="M12 17h.01" /></symbol>
|
||||
<symbol id="i-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2" /> <path d="M7 11V7a5 5 0 0 1 10 0v4" /></symbol>
|
||||
<symbol id="i-cloud" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" /></symbol>
|
||||
<symbol id="i-square" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" /></symbol>
|
||||
<symbol id="i-rotate-cw" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8" /> <path d="M21 3v5h-5" /></symbol>
|
||||
<symbol id="i-file-text" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z" /> <path d="M14 2v5a1 1 0 0 0 1 1h5" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></symbol>
|
||||
<symbol id="i-external-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6" /> <path d="M10 14 21 3" /> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /></symbol>
|
||||
<symbol id="i-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /></symbol>
|
||||
<symbol id="i-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2" /> <rect width="20" height="8" x="2" y="14" rx="2" ry="2" /> <line x1="6" x2="6.01" y1="6" y2="6" /> <line x1="6" x2="6.01" y1="18" y2="18" /></symbol>
|
||||
<symbol id="i-layout-grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1" /> <rect width="7" height="7" x="14" y="3" rx="1" /> <rect width="7" height="7" x="14" y="14" rx="1" /> <rect width="7" height="7" x="3" y="14" rx="1" /></symbol>
|
||||
<symbol id="i-settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" /> <circle cx="12" cy="12" r="3" /></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0" /> <path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></symbol>
|
||||
<symbol id="i-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18" /> <path d="m6 6 12 12" /></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></symbol>
|
||||
<symbol id="i-pencil" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" /> <path d="m15 5 4 4" /></symbol>
|
||||
<symbol id="i-info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10" /> <path d="M12 16v-4" /> <path d="M12 8h.01" /></symbol>
|
||||
<symbol id="i-download" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /> <path d="m7 10 5 5 5-5" /></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12" /> <path d="m17 8-5-5-5 5" /> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /></symbol>
|
||||
<symbol id="i-trash-2" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 11v6" /> <path d="M14 11v6" /> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" /> <path d="M3 6h18" /> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" /></symbol>
|
||||
<symbol id="i-wrench" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></symbol>
|
||||
<symbol id="i-link" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" /> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" /></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14" /> <path d="M12 5v14" /></symbol>
|
||||
<symbol id="i-chevron-down" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6" /></symbol>
|
||||
<symbol id="i-play" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z" /></symbol>
|
||||
<symbol id="i-pause" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="14" y="3" width="5" height="18" rx="1" /> <rect x="5" y="3" width="5" height="18" rx="1" /></symbol>
|
||||
</svg>
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<img src="/static/felhom-logo.svg" alt="Felhom.eu" class="sidebar-logo">
|
||||
<span class="customer-name">Demo Ügyfél</span>
|
||||
</div>
|
||||
<ul class="nav-links">
|
||||
<li><a href="/" class=""><svg class="ico"><use href="#i-layout-grid"/></svg>Vezérlőpult</a></li>
|
||||
<li><a href="/stacks" class=""><svg class="ico"><use href="#i-cloud"/></svg>Alkalmazások</a></li>
|
||||
<li><a href="/storage" class="active"><svg class="ico"><use href="#i-hard-drive"/></svg>Tárhely</a>
|
||||
<ul class="nav-links nav-links-sub nav-links-nested">
|
||||
<li><a href="/storage" class="">Meghajtók</a></li>
|
||||
<li><a href="/storage/network" class="active">Hálózati tárhely</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/backups" class=""><svg class="ico"><use href="#i-shield"/></svg>Biztonsági mentés</a></li>
|
||||
<li><a href="/monitoring" class=""><svg class="ico"><use href="#i-cpu"/></svg>Rendszermonitor</a></li>
|
||||
|
||||
</ul>
|
||||
<div class="sidebar-bottom">
|
||||
<div class="nav-group-label">Beállítások</div>
|
||||
<ul class="nav-links nav-links-sub">
|
||||
<li><a href="/settings" class=""><svg class="ico"><use href="#i-settings"/></svg>Rendszer</a></li>
|
||||
<li><a href="/settings/notifications" class=""><svg class="ico"><use href="#i-bell"/></svg>Értesítések</a></li>
|
||||
<li><a href="/settings/security" class=""><svg class="ico"><use href="#i-lock"/></svg>Biztonság és hozzáférés</a></li>
|
||||
</ul>
|
||||
<div class="sidebar-footer">
|
||||
<span class="version">0.102.0</span>
|
||||
<a href="/logout" class="logout-link">Kijelentkezés ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<main class="content">
|
||||
|
||||
<div class="alerts-container">
|
||||
|
||||
|
||||
<div class="alert-banner alert-banner-error">
|
||||
<span class="alert-icon"><svg class="ico"><use href="#i-triangle-alert"/></svg></span>
|
||||
<span class="alert-message">A tárolókezelő ügynök nem elérhető.</span>
|
||||
<a href="/settings" class="alert-link">Beállítások →</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<h2>Tárhely — Hálózati tárhely (NAS)</h2>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
|
||||
<div class="storage-section">
|
||||
<h3 style="margin-bottom:.25rem">NAS-megosztások</h3>
|
||||
<p class="form-hint" style="margin-top:0">
|
||||
Egy NAS-megosztás (NFS vagy SMB) csatlakoztatása nagy méretű médiatartalomhoz (film, fotó, zene).
|
||||
A megosztás kiválasztható médiaalkalmazás adatkönyvtáraként. A hálózati tárhely nem fizikai
|
||||
meghajtó — nincs leszerelés/áthelyezés, csak eltávolítás.
|
||||
</p>
|
||||
|
||||
<p class="form-hint">Nincs hálózati tárhely beállítva.</p>
|
||||
|
||||
|
||||
<details style="margin-top:.75rem">
|
||||
<summary class="btn btn-xs btn-primary" style="cursor:pointer;display:inline-block">Hálózati tárhely hozzáadása</summary>
|
||||
<div style="margin-top:.75rem;padding:1rem;border:1px solid var(--border,#ddd);border-radius:6px;max-width:520px">
|
||||
<div class="form-row"><label>Név (azonosító)</label>
|
||||
<input id="ns-name" type="text" placeholder="pl. media" class="form-input"></div>
|
||||
<div class="form-row"><label>Protokoll</label>
|
||||
<select id="ns-protocol" class="form-input" onchange="nsToggleSmb()">
|
||||
<option value="nfs">NFS (ajánlott)</option>
|
||||
<option value="smb">SMB / CIFS</option>
|
||||
</select></div>
|
||||
<div class="form-row"><label>Szerver (IP vagy hosztnév)</label>
|
||||
<input id="ns-server" type="text" placeholder="pl. 192.168.0.10" class="form-input"></div>
|
||||
<div class="form-row"><label id="ns-export-label">Megosztás (NFS export útvonal)</label>
|
||||
<input id="ns-export" type="text" placeholder="pl. /volume1/media" class="form-input"></div>
|
||||
<div class="form-row"><label>Alkalmazás felhasználói azonosító (uid)</label>
|
||||
<input id="ns-uid" type="number" value="1000" class="form-input">
|
||||
<span class="form-hint">A legtöbb médiaalkalmazás 1000-es uid-del fut.</span></div>
|
||||
<div id="ns-smb-creds" style="display:none">
|
||||
<div class="form-row"><label>SMB felhasználónév</label>
|
||||
<input id="ns-username" type="text" autocomplete="off" class="form-input"></div>
|
||||
<div class="form-row"><label>SMB jelszó</label>
|
||||
<input id="ns-password" type="password" autocomplete="new-password" class="form-input">
|
||||
<span class="form-hint">A jelszót a gazda ügynök 0600-as fájlba írja; a vezérlő nem tárolja.</span></div>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-primary" style="margin-top:.5rem" onclick="netStorageAdd()">Csatlakoztatás</button>
|
||||
<div id="ns-add-msg" class="form-hint" style="margin-top:.5rem"></div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
<script>
|
||||
function nsToggleSmb(){
|
||||
var smb = document.getElementById('ns-protocol').value === 'smb';
|
||||
document.getElementById('ns-smb-creds').style.display = smb ? 'block' : 'none';
|
||||
document.getElementById('ns-export-label').textContent = smb ? 'Megosztás (SMB megosztásnév)' : 'Megosztás (NFS export útvonal)';
|
||||
document.getElementById('ns-export').placeholder = smb ? 'pl. media' : 'pl. /volume1/media';
|
||||
}
|
||||
function netStorageAdd(){
|
||||
var msg = document.getElementById('ns-add-msg');
|
||||
var body = {
|
||||
name: (document.getElementById('ns-name').value||'').trim(),
|
||||
protocol: document.getElementById('ns-protocol').value,
|
||||
server: (document.getElementById('ns-server').value||'').trim(),
|
||||
export: (document.getElementById('ns-export').value||'').trim(),
|
||||
mapped_uid: parseInt(document.getElementById('ns-uid').value||'1000',10),
|
||||
mapped_gid: parseInt(document.getElementById('ns-uid').value||'1000',10),
|
||||
username: (document.getElementById('ns-username').value||''),
|
||||
password: (document.getElementById('ns-password').value||'')
|
||||
};
|
||||
msg.textContent = 'Csatlakoztatás folyamatban…';
|
||||
fetch('/api/storage/netstorage/add',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify(body)})
|
||||
.then(function(r){return r.json();}).then(function(d){
|
||||
if(d.ok){ msg.textContent='Sikeres'; setTimeout(function(){location.reload();},900); }
|
||||
else { msg.textContent='Hiba: '+(d.error||'ismeretlen'); }
|
||||
}).catch(function(e){ msg.textContent='Hiba: '+e; });
|
||||
}
|
||||
function netStorageRemove(name,label){
|
||||
openDialog({title:'Hálózati tárhely eltávolítása', confirmLabel:'Eltávolítás',
|
||||
message:'Biztosan eltávolítja a(z) '+label+' hálózati tárhelyet?\n\nA megosztás leválasztásra kerül; a NAS-on lévő adatok érintetlenek maradnak.',
|
||||
onConfirm:function(){
|
||||
fetch('/api/storage/netstorage/remove',{method:'POST',headers:Object.assign({'Content-Type':'application/json'},csrfHeaders()),body:JSON.stringify({name:name})})
|
||||
.then(function(r){return r.json();}).then(function(d){
|
||||
if(d.ok){ location.reload(); } else { alert('Hiba: '+(d.error||'ismeretlen')); }
|
||||
}).catch(function(e){ alert('Hiba: '+e); });
|
||||
}});
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
<div id="dialog-root"></div>
|
||||
<script>
|
||||
|
||||
function escText(s){var d=document.createElement('div');d.textContent=String(s==null?'':s);return d.innerHTML;}
|
||||
function closeDialog(){ var r=document.getElementById('dialog-root'); if(r) r.innerHTML=''; }
|
||||
function openDialog(opts){
|
||||
var root=document.getElementById('dialog-root');
|
||||
if(!root) return;
|
||||
root.innerHTML='<div class="confirm-overlay" onclick="if(event.target===this)document.getElementById(\'dialog-cancel\').click()"><div class="confirm-box">'
|
||||
+'<h3>'+escText(opts.title||'Megerősítés')+'</h3>'
|
||||
+'<p style="white-space:pre-line">'+escText(opts.message||'')+'</p>'
|
||||
+'<div class="form-actions"><button id="dialog-go" class="btn btn-primary">'+escText(opts.confirmLabel||'Megerősítés')+'</button>'
|
||||
+'<button type="button" class="btn btn-outline" id="dialog-cancel">Mégsem</button></div>'
|
||||
+'</div></div>';
|
||||
document.getElementById('dialog-go').onclick=function(){ closeDialog(); if(opts.onConfirm) opts.onConfirm(); };
|
||||
document.getElementById('dialog-cancel').onclick=function(){ closeDialog(); if(opts.onCancel) opts.onCancel(); };
|
||||
}
|
||||
</script>
|
||||
|
||||
</main>
|
||||
<script>
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.closest('a, button, .btn, input, select, textarea, .stack-actions, .stack-detail-actions')) return;
|
||||
var card = e.target.closest('[data-href]');
|
||||
if (card) window.location.href = card.dataset.href;
|
||||
});
|
||||
async function checkBeforeDeploy(e, name) {
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name);
|
||||
var data = await resp.json();
|
||||
if (data.ok && data.data && data.data.deployed) {
|
||||
e.preventDefault();
|
||||
showAlert('Ez az alkalmazás már telepítve van.');
|
||||
window.location.reload();
|
||||
return false;
|
||||
}
|
||||
} catch(err) {}
|
||||
return true;
|
||||
}
|
||||
async function syncTemplates() {
|
||||
const btn = document.getElementById('sync-btn');
|
||||
const toast = document.getElementById('sync-toast');
|
||||
if (!btn) return;
|
||||
const origText = btn.innerHTML;
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '↻ Frissítés...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/sync', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (toast) {
|
||||
toast.textContent = data.ok ? (data.message || 'Sablonok frissítve') : ('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
toast.className = 'sync-toast ' + (data.ok ? 'sync-toast-ok' : 'sync-toast-err');
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
if (data.ok && data.data && (data.data.new_apps && data.data.new_apps.length > 0 || data.data.updated && data.data.updated.length > 0)) {
|
||||
setTimeout(function() { window.location.reload(); }, 1500);
|
||||
}
|
||||
} catch (err) {
|
||||
if (toast) {
|
||||
toast.textContent = 'Hálózati hiba: ' + err.message;
|
||||
toast.className = 'sync-toast sync-toast-err';
|
||||
toast.style.display = 'block';
|
||||
setTimeout(function() { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
}
|
||||
btn.innerHTML = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
async function stackAction(event, name, action) {
|
||||
const btn = event.currentTarget;
|
||||
const origText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Folyamatban...';
|
||||
btn.classList.add('loading');
|
||||
try {
|
||||
const resp = await fetch('/api/stacks/' + name + '/' + action, {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders())
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!data.ok) {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
return;
|
||||
}
|
||||
window.location.reload();
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.textContent = origText;
|
||||
btn.disabled = false;
|
||||
btn.classList.remove('loading');
|
||||
}
|
||||
}
|
||||
async function deleteOrphanStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'delete-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeDeleteModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/hdd-data');
|
||||
var data = await resp.json();
|
||||
var hddInfo = '';
|
||||
var checkboxHTML = '';
|
||||
if (data.ok && data.data && data.data.has_hdd_data) {
|
||||
hddInfo = '<div class="modal-hdd-info"><strong>Felhasználói adatok a merevlemezen:</strong>';
|
||||
data.data.hdd_paths.forEach(function(p) {
|
||||
hddInfo += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
hddInfo += '</div>';
|
||||
checkboxHTML = '<label class="modal-checkbox"><input type="checkbox" id="delete-hdd-check"> Felhasználói adatok törlése a merevlemezről</label>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás törlése: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Ez a művelet eltávolítja a konténereket, a köteteket és a konfigurációs fájlokat.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
hddInfo + checkboxHTML +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeDeleteModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-delete-btn" onclick="confirmDelete(\'' + name + '\')">Törlés</button>' +
|
||||
'</div>';
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeDeleteModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeDeleteModal() {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmDelete(name) {
|
||||
var btn = document.getElementById('confirm-delete-btn');
|
||||
var checkbox = document.getElementById('delete-hdd-check');
|
||||
var removeHDD = checkbox ? checkbox.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Törlés folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name, {
|
||||
method: 'DELETE',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('delete-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen törölve!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') törölve lett.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Törlés';
|
||||
}
|
||||
}
|
||||
async function removeStack(name) {
|
||||
var modal = document.createElement('div');
|
||||
modal.className = 'modal-overlay';
|
||||
modal.id = 'remove-modal';
|
||||
modal.innerHTML = '<div class="modal-card"><h3>Betöltés...</h3></div>';
|
||||
modal.addEventListener('click', function(e) { if (e.target === modal) closeRemoveModal(); });
|
||||
document.body.appendChild(modal);
|
||||
try {
|
||||
var [hddResp, backupResp] = await Promise.all([
|
||||
fetch('/api/stacks/' + name + '/hdd-data').then(function(r) { return r.json(); }),
|
||||
fetch('/api/stacks/' + name + '/backup-data').then(function(r) { return r.json(); })
|
||||
]);
|
||||
var sections = '';
|
||||
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mindig törlődik:</strong>' +
|
||||
'<ul style="margin:.25rem 0;padding-left:1.2rem;color:var(--text-2);font-size:.85rem">' +
|
||||
'<li>Docker kötetek (adatbázis, alkalmazás konfiguráció)</li>' +
|
||||
'<li>Telepítési konfiguráció (app.yaml)</li>' +
|
||||
'<li>Másodlagos mentés ütemezése</li>' +
|
||||
'</ul></div>';
|
||||
|
||||
var hddCheckbox = '';
|
||||
if (hddResp.ok && hddResp.data && hddResp.data.has_hdd_data) {
|
||||
var hddPaths = '';
|
||||
hddResp.data.hdd_paths.forEach(function(p) {
|
||||
hddPaths += '<div class="modal-hdd-path">' + p.path + ' (' + (p.exists ? p.size_human : 'nem létezik') + ')</div>';
|
||||
});
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Felhasználói adatok a merevlemezen:</strong>' + hddPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-hdd-check"> Felhasználói adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem" id="remove-hdd-keep-warning">' +
|
||||
'Ha újratelepíti az alkalmazást, az adatokat újra importálnia kell, mivel az adatbázis törlődik. A megtartott adatok a továbbiakban NEM lesznek automatikusan mentve.' +
|
||||
'</div></div>';
|
||||
hddCheckbox = '\x3Cscript>document.getElementById("remove-hdd-check").addEventListener("change",function(){document.getElementById("remove-hdd-keep-warning").style.display=this.checked?"none":"";});<\/script>';
|
||||
}
|
||||
|
||||
var backupCheckbox = '';
|
||||
if (backupResp.ok && backupResp.data && backupResp.data.has_backups) {
|
||||
var bkPaths = '';
|
||||
backupResp.data.backup_paths.forEach(function(p) {
|
||||
if (p.exists) bkPaths += '<div class="modal-hdd-path">' + p.path + ' (' + p.size_human + ')</div>';
|
||||
});
|
||||
if (bkPaths) {
|
||||
sections += '<div class="modal-section">' +
|
||||
'<strong>Mentési adatok:</strong>' + bkPaths +
|
||||
'<label class="modal-checkbox"><input type="checkbox" id="remove-backup-check"> Mentési adatok törlése</label>' +
|
||||
'<div class="alert alert-info" style="margin-top:.5rem;font-size:.8rem">' +
|
||||
'Az éjszakai restic pillanatképek nem törölhetők egyenként — a megőrzési szabályok szerint automatikusan elavulnak.' +
|
||||
'</div></div>';
|
||||
}
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Alkalmazás eltávolítása: ' + name + '</h3>' +
|
||||
'<p style="color:var(--text-2);font-size:.9rem;margin-bottom:.75rem">Az alkalmazás visszaáll "Nincs telepítve" állapotba. A sablon megmarad, újratelepíthető.</p>' +
|
||||
'<div class="alert alert-warning" style="margin-bottom:.75rem">Ez a művelet nem visszavonható!</div>' +
|
||||
sections +
|
||||
'<div class="modal-actions">' +
|
||||
'<button class="btn btn-outline" onclick="closeRemoveModal()">Mégsem</button>' +
|
||||
'<button class="btn btn-danger" id="confirm-remove-btn" onclick="confirmRemoveStack(\'' + name + '\')">Eltávolítás</button>' +
|
||||
'</div>' + hddCheckbox;
|
||||
} catch (err) {
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Hiba</h3><p style="color:var(--text-2)">Nem sikerült lekérni az adatokat: ' + err.message + '</p>' +
|
||||
'<div class="modal-actions"><button class="btn btn-outline" onclick="closeRemoveModal()">Bezárás</button></div>';
|
||||
}
|
||||
}
|
||||
function closeRemoveModal() {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
async function confirmRemoveStack(name) {
|
||||
var btn = document.getElementById('confirm-remove-btn');
|
||||
var hddCheck = document.getElementById('remove-hdd-check');
|
||||
var backupCheck = document.getElementById('remove-backup-check');
|
||||
var removeHDD = hddCheck ? hddCheck.checked : false;
|
||||
var removeBackups = backupCheck ? backupCheck.checked : false;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Eltávolítás folyamatban...';
|
||||
try {
|
||||
var resp = await fetch('/api/stacks/' + name + '/remove', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
||||
body: JSON.stringify({remove_hdd_data: removeHDD, remove_backups: removeBackups})
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (data.ok) {
|
||||
var modal = document.getElementById('remove-modal');
|
||||
var removedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_removed && data.data.hdd_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt adatok: ' + data.data.hdd_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
if (data.data && data.data.backup_paths_removed && data.data.backup_paths_removed.length > 0) {
|
||||
removedInfo += '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Törölt mentések: ' + data.data.backup_paths_removed.join(', ') + '</p>';
|
||||
}
|
||||
var preservedInfo = '';
|
||||
if (data.data && data.data.hdd_paths_preserved && data.data.hdd_paths_preserved.length > 0) {
|
||||
preservedInfo = '<p style="color:var(--text-2);font-size:.85rem;margin-top:.5rem">Megőrzött adatok: ' + data.data.hdd_paths_preserved.join(', ') + '</p>';
|
||||
}
|
||||
modal.querySelector('.modal-card').innerHTML =
|
||||
'<h3>Sikeresen eltávolítva!</h3>' +
|
||||
'<p style="color:var(--text-2)">Az alkalmazás (' + name + ') eltávolítva. Újratelepíthető a Telepítés gombbal.</p>' +
|
||||
removedInfo + preservedInfo +
|
||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||
} else {
|
||||
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
} catch (err) {
|
||||
showAlert('Hálózati hiba: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Eltávolítás';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
/ 200 Áttekintés no
|
||||
/stacks 200 Alkalmazások yes
|
||||
/backups 200 Mentés yes
|
||||
/monitoring 200 Monitor no
|
||||
/storage 200 Tárhely yes
|
||||
/storage/network 200 Hálózati yes
|
||||
/settings 200 Beállítások yes
|
||||
/settings/notifications 200 Értesítés yes
|
||||
/settings/security 200 Biztonság yes
|
||||
/import 200 Import yes
|
||||
/debug 404 Debug no
|
||||
/apps/vaultwarden 200 Vaultwarden yes
|
||||
/apps/paperless-ngx 200 Paperless yes
|
||||
|
@@ -0,0 +1 @@
|
||||
total_recover_s=1202
|
||||
@@ -0,0 +1 @@
|
||||
total_recover_s=1208
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
DRYRUN
|
||||
200
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"ok":false,"error":"authentication required"}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user