v0.319.0: the app page in panels, the login in one place, a password the box never had is said in words
gates / gates (push) Successful in 52s
gates / gates (push) Successful in 52s
- style A (operator 2026-10-16): hero, tab bar and every section are panels on --bg-1, one width, 16px apart; the install page's form groups use the same rule; the settings tab's 700px inner wrapper is gone - „Belépés az alkalmazásba": name + password side by side, fetched on demand (R-254, R-709); pairing in app_login.go - the empty-value rule: no stored value -> a sentence, never an eye; page and reveal endpoint share storedValuePresent - „Cím és kulcsok" is rows (no Domain input, no disabled storage select, no install form); hint under the title - tests red-proved: app_page_tuning_test.go (4); 36 app-page parity fixtures re-captured, 4 new; install fixtures unchanged Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
This commit is contained in:
@@ -118,6 +118,11 @@
|
||||
"app_page.address_and_keys": "Address and keys",
|
||||
"app_page.address": "Address",
|
||||
"app_page.installed_readonly": "These were created at install time and cannot be changed.",
|
||||
"app_page.login_title": "Signing in to the app",
|
||||
"app_page.login_desc": "For the first sign-in. If you change the password in the app, the old one stays here.",
|
||||
"app_page.login_no_password": "The box saved no password for this install. Sign in with the one you set in the app.",
|
||||
"app_page.login_no_name": "The box saved no user name for this install. Sign in with the one you set in the app.",
|
||||
"app_page.value_no": "No",
|
||||
"app_page.after_start_btn": "Save",
|
||||
"app_page.after_start_title": "One more step",
|
||||
"app_page.faq": "Frequently asked questions",
|
||||
|
||||
@@ -114,6 +114,11 @@
|
||||
"app_page.address_and_keys": "Cím és kulcsok",
|
||||
"app_page.address": "Cím",
|
||||
"app_page.installed_readonly": "Ezek telepítéskor jöttek létre, és nem módosíthatók.",
|
||||
"app_page.login_title": "Belépés az alkalmazásba",
|
||||
"app_page.login_desc": "Az első bejelentkezéshez. Ha az alkalmazásban megváltoztatod a jelszót, itt a régi marad.",
|
||||
"app_page.login_no_password": "A doboz ennél a telepítésnél nem mentett jelszót. A belépéshez azt használd, amit az alkalmazásban beállítottál.",
|
||||
"app_page.login_no_name": "A doboz ennél a telepítésnél nem mentett felhasználónevet. A belépéshez azt használd, amit az alkalmazásban beállítottál.",
|
||||
"app_page.value_no": "Nem",
|
||||
"app_page.after_start_btn": "Mentés",
|
||||
"app_page.after_start_title": "Még egy lépés",
|
||||
"app_page.faq": "Gyakori kérdések",
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
)
|
||||
|
||||
// THE LOGIN PANEL AND THE „CIM ES KULCSOK" ROWS (v0.319.0) — the settings tab's view of what was
|
||||
// set at install.
|
||||
//
|
||||
// Until v0.318.0 the settings tab re-rendered the install wizard's form with every input disabled:
|
||||
// the generated login name sat in „Automatikusan generalt ertekek" next to a disabled Domain input,
|
||||
// and the password sat three sections lower among the other install answers. The household had to
|
||||
// find two halves of one login. Now one panel holds the pair, and the rest are label/value rows.
|
||||
//
|
||||
// THE EMPTY-VALUE RULE. A field the box holds no value for is SAID, never offered an eye: the reveal
|
||||
// endpoint answers 404 for it („Ehhez a mezohoz nincs mentett ertek"), which is a refusal the
|
||||
// household could only meet by pressing. Measured 2026-10-16 on demo-hp: Calibre-Web was installed
|
||||
// on 2026-08-21, its template gained ADMIN_PASSWORD on 2026-09-29, and InjectMissingFields never
|
||||
// fills a `type: password` field — so the box never had one (audits/app-page-tuning-2026-10-16/P0).
|
||||
// The page and the endpoint decide „stored" with ONE function, storedValuePresent, so the page
|
||||
// cannot offer what the endpoint would refuse. Pinned by TestAppPage_NoStoredValueShowsSentence.
|
||||
//
|
||||
// SECRETS. A `type: secret` name and every `type: password` value stay off the page (R-254, R-709);
|
||||
// only the boolean Stored reaches the template. A `type: text` name is not a secret — the old
|
||||
// disabled input carried it as its value — and is shown as text.
|
||||
|
||||
// storedValuePresent is the one definition of „the box holds a value for this field", shared by the
|
||||
// settings tab and appAutoFieldRevealHandler's 404 branch.
|
||||
func storedValuePresent(val string) bool { return strings.TrimSpace(val) != "" }
|
||||
|
||||
// AppLoginField is one half of the login panel. Value is set ONLY for a plain-text name; a secret
|
||||
// or a password never carries its value here.
|
||||
type AppLoginField struct {
|
||||
EnvVar string
|
||||
Label string
|
||||
IsName bool // the name half; false for the password half
|
||||
OnDemand bool // fetched on demand through /stacks/{name}/auto-field/reveal
|
||||
Value string // a `type: text` name only
|
||||
Stored bool // the box holds a non-blank value
|
||||
Restored bool // R-694: a restore generated it, so it is not the app's login and is never shown
|
||||
}
|
||||
|
||||
// AppSettingRow is one label/value row of „Cim es kulcsok". An OnDemand row carries no value; the
|
||||
// eye fetches it.
|
||||
type AppSettingRow struct {
|
||||
EnvVar string
|
||||
Label string
|
||||
Value string
|
||||
OnDemand bool
|
||||
}
|
||||
|
||||
// loginNameSuffixes are the env-var endings the catalog uses for the name next to a password:
|
||||
// ADMIN_USER + ADMIN_PASSWORD (calibre-web), ADMIN_EMAIL + ADMIN_PASSWORD (kimai),
|
||||
// NEXTCLOUD_ADMIN_USER + NEXTCLOUD_ADMIN_PASSWORD, and so on — measured over the catalog on
|
||||
// 2026-10-16, every pair shares the prefix.
|
||||
var loginNameSuffixes = []string{"USER", "USERNAME", "EMAIL"}
|
||||
|
||||
// loginFieldIndexes returns, in deploy_fields order, the indexes of the login fields: every
|
||||
// `type: password` field (not asked after start), each preceded by its name field when the
|
||||
// template has one with the same prefix and type `secret` or `text`. A password with no clean
|
||||
// pair stands alone.
|
||||
func loginFieldIndexes(meta *stacks.Metadata) []int {
|
||||
if meta == nil {
|
||||
return nil
|
||||
}
|
||||
byEnv := make(map[string]int, len(meta.DeployFields))
|
||||
for i, f := range meta.DeployFields {
|
||||
byEnv[f.EnvVar] = i
|
||||
}
|
||||
var out []int
|
||||
for i, f := range meta.DeployFields {
|
||||
if f.Type != "password" || f.AfterStart {
|
||||
continue
|
||||
}
|
||||
// ADMIN_PASSWORD -> prefix "ADMIN_" -> ADMIN_USER / ADMIN_USERNAME / ADMIN_EMAIL.
|
||||
prefix := strings.TrimSuffix(f.EnvVar, "PASSWORD")
|
||||
if prefix != f.EnvVar {
|
||||
for _, suf := range loginNameSuffixes {
|
||||
if j, ok := byEnv[prefix+suf]; ok {
|
||||
if t := meta.DeployFields[j].Type; (t == "secret" || t == "text") && !meta.DeployFields[j].AfterStart {
|
||||
out = append(out, j)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, i)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// appLoginFields builds the panel from the DECRYPTED env (before any value is removed from it).
|
||||
func appLoginFields(meta *stacks.Metadata, env map[string]string, restored map[string]bool) []AppLoginField {
|
||||
var out []AppLoginField
|
||||
for _, i := range loginFieldIndexes(meta) {
|
||||
f := meta.DeployFields[i]
|
||||
lf := AppLoginField{EnvVar: f.EnvVar, Label: f.Label, IsName: true, Stored: storedValuePresent(env[f.EnvVar])}
|
||||
switch f.Type {
|
||||
case "password":
|
||||
lf.IsName, lf.OnDemand, lf.Restored = false, true, restored[f.EnvVar]
|
||||
case "secret":
|
||||
lf.OnDemand = true
|
||||
default:
|
||||
lf.Value = env[f.EnvVar]
|
||||
}
|
||||
out = append(out, lf)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// appSettingRows is everything else an installed app was given at install, as rows: the generated
|
||||
// secrets (eye) and the plain answers (text). Skipped, because the page says them elsewhere: the
|
||||
// domain and the subdomain (the „Cim" row), the data folder (the „Tarhely" row), the login fields
|
||||
// (the login panel), and a field asked after start (its own card).
|
||||
func appSettingRows(meta *stacks.Metadata, env map[string]string, yes, no string) []AppSettingRow {
|
||||
if meta == nil {
|
||||
return nil
|
||||
}
|
||||
login := map[int]bool{}
|
||||
for _, i := range loginFieldIndexes(meta) {
|
||||
login[i] = true
|
||||
}
|
||||
var out []AppSettingRow
|
||||
for i, f := range meta.DeployFields {
|
||||
if login[i] || f.AfterStart {
|
||||
continue
|
||||
}
|
||||
val := env[f.EnvVar]
|
||||
switch f.Type {
|
||||
case "domain", "subdomain", "path", "password":
|
||||
continue
|
||||
case "secret":
|
||||
if storedValuePresent(val) {
|
||||
out = append(out, AppSettingRow{EnvVar: f.EnvVar, Label: f.Label, OnDemand: true})
|
||||
}
|
||||
continue
|
||||
case "select":
|
||||
for _, o := range f.Options {
|
||||
if o.Value == val {
|
||||
val = o.Label
|
||||
break
|
||||
}
|
||||
}
|
||||
case "boolean":
|
||||
if val == "true" {
|
||||
val = yes
|
||||
} else {
|
||||
val = no
|
||||
}
|
||||
}
|
||||
if !storedValuePresent(val) {
|
||||
continue
|
||||
}
|
||||
out = append(out, AppSettingRow{EnvVar: f.EnvVar, Label: f.Label, Value: val})
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,434 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/config"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/settings"
|
||||
"gitea.dooplex.hu/admin/felhom-controller/internal/stacks"
|
||||
)
|
||||
|
||||
// APP PAGE TUNING, ROUND 2 (v0.319.0) — the login panel, the empty-value rule, the settings tab
|
||||
// without the install form's duplicates, and one section style on every surface.
|
||||
//
|
||||
// The page tests go through s.ServeHTTP (the production route switch and handler), because the
|
||||
// thing under test is what the HANDLER tells the template: a template rendered with hand-built
|
||||
// LoginFields would assert nothing about whether the box's own values stay out of the page.
|
||||
// Assertions are on ASCII `data-*` markers and ids, never on accented Hungarian.
|
||||
|
||||
// The two values the box holds for an installed Calibre-Web. Neither may appear in any page.
|
||||
const (
|
||||
tuningUser = "TESTONLY-user-q7x"
|
||||
tuningPassword = "TESTONLY-pw-Lm93!"
|
||||
tuningKey = "TESTONLY-key-Zr55"
|
||||
)
|
||||
|
||||
// calibreMeta is the catalog's calibre-web deploy_fields (templates/calibre-web/.felhom.yml, read
|
||||
// 2026-10-16), plus one generated key so a non-login secret row is exercised too.
|
||||
const calibreMeta = "display_name: Calibre-Web\nslug: calibre-web\nsubdomain: books\n" +
|
||||
"deploy_fields:\n" +
|
||||
" - env_var: DOMAIN\n label: Domain\n type: domain\n locked_after_deploy: true\n" +
|
||||
" - env_var: SUBDOMAIN\n label: Aldomain\n type: subdomain\n default: books\n required: true\n" +
|
||||
" - env_var: HDD_PATH\n label: Konyvtar utvonal\n type: path\n required: true\n" +
|
||||
" - env_var: ADMIN_USER\n label: Admin nev\n type: secret\n generate: \"hex:5\"\n" +
|
||||
" - env_var: ADMIN_PASSWORD\n label: Admin jelszo\n type: password\n generate: \"password:24:special\"\n" +
|
||||
" - env_var: SECRET_KEY\n label: Titkos kulcs\n type: secret\n generate: \"hex:16\"\n"
|
||||
|
||||
// tuningHarness: `calibre-web` installed with the given env, and `calibre-new` — the same template,
|
||||
// not installed — for the install page.
|
||||
func tuningHarness(t *testing.T, env map[string]string) *Server {
|
||||
t.Helper()
|
||||
dir := t.TempDir()
|
||||
bin := filepath.Join(dir, "bin")
|
||||
if err := os.MkdirAll(bin, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
stub := "#!/bin/sh\nif [ \"$1\" = ps ]; then\n printf 'calibre-web\\tbusybox\\trunning\\tUp 2 hours\\tcalibre-web\\n'\nfi\nexit 0\n"
|
||||
if err := os.WriteFile(filepath.Join(bin, "docker"), []byte(stub), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Setenv("PATH", bin)
|
||||
|
||||
lg := log.New(io.Discard, "", 0)
|
||||
cfg := config.Default()
|
||||
cfg.Customer.Domain = "example.hu"
|
||||
cfg.Paths.StacksDir = filepath.Join(dir, "stacks")
|
||||
cfg.Paths.DataDir = filepath.Join(dir, "data")
|
||||
for _, app := range []string{"calibre-web", "calibre-new"} {
|
||||
d := filepath.Join(cfg.Paths.StacksDir, app)
|
||||
if err := os.MkdirAll(d, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
meta := strings.Replace(calibreMeta, "slug: calibre-web", "slug: "+app, 1)
|
||||
for name, body := range map[string]string{
|
||||
"docker-compose.yml": "services:\n " + app + ":\n image: busybox\n",
|
||||
".felhom.yml": meta,
|
||||
} {
|
||||
if err := os.WriteFile(filepath.Join(d, name), []byte(body), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := stacks.SaveAppConfig(filepath.Join(cfg.Paths.StacksDir, "calibre-web"),
|
||||
&stacks.AppConfig{Deployed: true, Env: env}, nil, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sett, err := settings.Load(filepath.Join(dir, "settings.json"), lg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := sett.AddStoragePath(settings.StoragePath{Path: "/mnt/felhom-drives/hdd_1", Label: "Kulso lemez", IsDefault: true, Schedulable: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mgr, err := stacks.NewManager(cfg, lg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_ = mgr.ScanStacks()
|
||||
if _, ok := mgr.GetStack("calibre-web"); !ok {
|
||||
t.Fatal("calibre-web was not discovered by ScanStacks")
|
||||
}
|
||||
s := &Server{cfg: cfg, settings: sett, stackMgr: mgr, logger: lg, version: "test"}
|
||||
s.loadTemplates()
|
||||
return s
|
||||
}
|
||||
|
||||
func tuningEnv() map[string]string {
|
||||
return map[string]string{
|
||||
"DOMAIN": "example.hu", "SUBDOMAIN": "books", "HDD_PATH": "/mnt/felhom-drives/hdd_1",
|
||||
"ADMIN_USER": tuningUser, "ADMIN_PASSWORD": tuningPassword, "SECRET_KEY": tuningKey,
|
||||
}
|
||||
}
|
||||
|
||||
// revealStatus asks the reveal endpoint exactly as the eye button does, and returns its status.
|
||||
func revealStatus(s *Server, envVar string) int {
|
||||
r := httptest.NewRequest(http.MethodPost, "/stacks/calibre-web/auto-field/reveal", strings.NewReader("env_var="+envVar))
|
||||
r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w := httptest.NewRecorder()
|
||||
s.appAutoFieldRevealHandler(w, r, "calibre-web")
|
||||
return w.Code
|
||||
}
|
||||
|
||||
// sectionOf returns the element that starts at the first `marker` up to its matching </div>.
|
||||
func sectionOf(t *testing.T, body, marker string) string {
|
||||
t.Helper()
|
||||
i := strings.Index(body, marker)
|
||||
if i < 0 {
|
||||
t.Fatalf("no %s in the page", marker)
|
||||
}
|
||||
start := strings.LastIndex(body[:i], "<div")
|
||||
depth := 0
|
||||
for _, m := range divTagRe.FindAllStringIndex(body[start:], -1) {
|
||||
if strings.HasPrefix(body[start+m[0]:], "</") {
|
||||
depth--
|
||||
} else {
|
||||
depth++
|
||||
}
|
||||
if depth == 0 {
|
||||
return body[start : start+m[1]]
|
||||
}
|
||||
}
|
||||
t.Fatalf("%s: unbalanced divs", marker)
|
||||
return ""
|
||||
}
|
||||
|
||||
var divTagRe = regexp.MustCompile(`</?div\b[^>]*>`)
|
||||
|
||||
var revealCallRe = regexp.MustCompile(`revealAutoField\('calibre-web','([A-Z_]+)'`)
|
||||
|
||||
// TestAppPage_LoginPairPanel (Scenario B) — ONE „Belepes az alkalmazasba" panel holds the name and
|
||||
// the password, name first; both are fetched on demand, so neither value is in the page; and the
|
||||
// eye on each is one the endpoint answers.
|
||||
//
|
||||
// RED-PROOF (REPORT.md): render the panel without the name (drop the pairing loop in
|
||||
// loginFieldIndexes) -> the ADMIN_USER assertion fails. Reverted.
|
||||
func TestAppPage_LoginPairPanel(t *testing.T) {
|
||||
s := tuningHarness(t, tuningEnv())
|
||||
rec := appGet(t, s, "/apps/calibre-web/settings")
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("GET settings = %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
body := rec.Body.String()
|
||||
for _, v := range []string{tuningUser, tuningPassword, tuningKey} {
|
||||
if strings.Contains(body, v) {
|
||||
t.Fatalf("a stored secret value is in the settings page (R-254/R-709)")
|
||||
}
|
||||
}
|
||||
if n := strings.Count(body, `data-login-panel="1"`); n != 1 {
|
||||
t.Fatalf("%d login panels, want exactly 1", n)
|
||||
}
|
||||
panel := sectionOf(t, body, `data-login-panel="1"`)
|
||||
u, p := strings.Index(panel, `data-login-field="ADMIN_USER"`), strings.Index(panel, `data-login-field="ADMIN_PASSWORD"`)
|
||||
if u < 0 || p < 0 {
|
||||
t.Fatalf("the login panel must hold BOTH halves (user=%d password=%d)", u, p)
|
||||
}
|
||||
if u > p {
|
||||
t.Error("the name must come before the password")
|
||||
}
|
||||
if !strings.Contains(panel, `class="app-login-pair"`) {
|
||||
t.Error("the pair is not laid out side by side (app-login-pair)")
|
||||
}
|
||||
for _, f := range []string{"ADMIN_USER", "ADMIN_PASSWORD"} {
|
||||
if !strings.Contains(panel, `data-login-reveal="`+f+`"`) {
|
||||
t.Errorf("no eye for %s in the login panel", f)
|
||||
}
|
||||
if n := strings.Count(body, `id="field-`+f+`"`); n != 1 {
|
||||
t.Errorf("field %s shown %d times, want once (Scenario E)", f, n)
|
||||
}
|
||||
}
|
||||
// The login fields are shown ONCE: not also as a „Cim es kulcsok" row.
|
||||
if strings.Contains(body, `data-setting-row="ADMIN_USER"`) || strings.Contains(body, `data-setting-row="ADMIN_PASSWORD"`) {
|
||||
t.Error("a login field is shown twice — in the panel and as a row")
|
||||
}
|
||||
// Every eye on the page is one the endpoint answers with a value.
|
||||
for _, m := range revealCallRe.FindAllStringSubmatch(body, -1) {
|
||||
if code := revealStatus(s, m[1]); code != http.StatusOK {
|
||||
t.Errorf("the page offers an eye for %s and the endpoint answers %d", m[1], code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAppPage_NoStoredValueShowsSentence (Scenario C) — the box holds no password (Calibre-Web
|
||||
// installed before its template had one, measured on demo-hp 2026-10-16): the page says so in a
|
||||
// sentence and offers no eye, because the endpoint would answer 404 for it.
|
||||
//
|
||||
// RED-PROOF (REPORT.md): render the eye regardless (Stored: true in appLoginFields) -> the
|
||||
// no-eye and the every-eye-answers assertions fail. Reverted.
|
||||
func TestAppPage_NoStoredValueShowsSentence(t *testing.T) {
|
||||
env := tuningEnv()
|
||||
delete(env, "ADMIN_PASSWORD")
|
||||
env["ADMIN_USER"] = " " // a blank value is no value: the endpoint's own rule
|
||||
s := tuningHarness(t, env)
|
||||
body := appGet(t, s, "/apps/calibre-web/settings").Body.String()
|
||||
|
||||
for _, f := range []string{"ADMIN_PASSWORD", "ADMIN_USER"} {
|
||||
if code := revealStatus(s, f); code != http.StatusNotFound {
|
||||
t.Fatalf("precondition: the endpoint must answer 404 for %s, got %d", f, code)
|
||||
}
|
||||
if !strings.Contains(body, `data-login-not-stored="`+f+`"`) {
|
||||
t.Errorf("%s: no sentence for a field the box holds no value for", f)
|
||||
}
|
||||
if strings.Contains(body, `data-login-reveal="`+f+`"`) || strings.Contains(body, `revealAutoField('calibre-web','`+f+`'`) {
|
||||
t.Errorf("%s: an eye is offered for a value the endpoint answers 404 for", f)
|
||||
}
|
||||
}
|
||||
for _, m := range revealCallRe.FindAllStringSubmatch(body, -1) {
|
||||
if code := revealStatus(s, m[1]); code != http.StatusOK {
|
||||
t.Errorf("the page offers an eye for %s and the endpoint answers %d", m[1], code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAppPage_SettingsHasNoDuplicateDomain (Scenario E) — the settings tab carries no Domain input
|
||||
// (the address is the „Cim" row), no disabled storage select (it is the „Tarhely" row), no install
|
||||
// form, and the read-only hint sits under the „Cim es kulcsok" title. The install page keeps its
|
||||
// fields (Scenario D's byte-identity is the parity fixtures' job; this asserts the fields are there).
|
||||
//
|
||||
// RED-PROOF (REPORT.md): put `{{template "app_deploy_fields" .}}` back into the settings tab -> the
|
||||
// Domain/deploy-form assertions fail. Reverted.
|
||||
func TestAppPage_SettingsHasNoDuplicateDomain(t *testing.T) {
|
||||
s := tuningHarness(t, tuningEnv())
|
||||
body := appGet(t, s, "/apps/calibre-web/settings").Body.String()
|
||||
for _, gone := range []string{`id="auto-field-DOMAIN"`, `id="field-DOMAIN"`, `id="deploy-form"`, `id="field-HDD_PATH"`, `id="field-SUBDOMAIN"`} {
|
||||
if strings.Contains(body, gone) {
|
||||
t.Errorf("the settings tab still renders %s", gone)
|
||||
}
|
||||
}
|
||||
if n := strings.Count(body, `data-app-address-row="1"`); n != 1 {
|
||||
t.Errorf("%d address rows, want 1", n)
|
||||
}
|
||||
keys := sectionOf(t, body, `data-address-keys="1"`)
|
||||
hint, addr := strings.Index(keys, `data-readonly-hint="1"`), strings.Index(keys, `data-app-address-row="1"`)
|
||||
if hint < 0 || addr < 0 || hint > addr {
|
||||
t.Errorf("the read-only hint must sit under the title, above the rows (hint=%d address=%d)", hint, addr)
|
||||
}
|
||||
if strings.Count(body, `data-readonly-hint="1"`) != 1 {
|
||||
t.Error("the read-only hint must appear once, inside the address-and-keys section")
|
||||
}
|
||||
if row := sectionOf(t, keys, `data-app-storage-row="1"`); !strings.Contains(row, "Kulso lemez") {
|
||||
t.Errorf("the storage row must name the app's drive, got %q", row)
|
||||
}
|
||||
if !strings.Contains(keys, `data-setting-row="SECRET_KEY"`) {
|
||||
t.Error("a generated non-login key must still be a row with its eye")
|
||||
}
|
||||
|
||||
inst := appGet(t, s, "/apps/calibre-new/install")
|
||||
if inst.Code != http.StatusOK {
|
||||
t.Fatalf("GET install = %d", inst.Code)
|
||||
}
|
||||
ib := inst.Body.String()
|
||||
for _, want := range []string{`id="deploy-form"`, `id="field-SUBDOMAIN"`, `id="field-ADMIN_PASSWORD"`, `id="auto-field-ADMIN_USER"`, `generatePassword('field-ADMIN_PASSWORD'`} {
|
||||
if !strings.Contains(ib, want) {
|
||||
t.Errorf("the install page lost %s", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── one section style ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
var (
|
||||
tagRe = regexp.MustCompile(`<(/?)([a-zA-Z][a-zA-Z0-9]*)([^>]*?)(/?)>`)
|
||||
classAttrRe = regexp.MustCompile(`class="([^"]*)"`)
|
||||
stripBlockRe = regexp.MustCompile(`(?s)<script\b.*?</script>|<style\b.*?</style>|<!--.*?-->`)
|
||||
voidTags = map[string]bool{"input": true, "img": true, "br": true, "hr": true, "meta": true, "link": true, "source": true, "use": true, "path": true}
|
||||
// Wrappers that carry no look of their own: their children are the page's sections.
|
||||
transparentWrappers = []string{"app-sections", "app-guide", "app-data-tab", "app-settings-tab"}
|
||||
// Not sections: the save/failure flash lines at the top of a tab.
|
||||
messageClasses = []string{"flash", "monitoring-banner"}
|
||||
)
|
||||
|
||||
func hasClass(attrs, want string) bool {
|
||||
m := classAttrRe.FindStringSubmatch(attrs)
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
for _, c := range strings.Fields(m[1]) {
|
||||
if c == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tabTopLevelBlocks returns, for a rendered app-page TAB, every block the household sees between the
|
||||
// tab bar and the end of the column — looking through the transparent wrappers — as its opening tag.
|
||||
func tabTopLevelBlocks(t *testing.T, where, body string) []string {
|
||||
t.Helper()
|
||||
body = stripBlockRe.ReplaceAllString(body, "")
|
||||
nav := strings.Index(body, `class="app-tabs"`)
|
||||
if nav < 0 {
|
||||
t.Fatalf("%s: no tab bar", where)
|
||||
}
|
||||
end := strings.Index(body[nav:], "</nav>")
|
||||
rest := body[nav+end+len("</nav>"):]
|
||||
var stack []bool // true = transparent wrapper
|
||||
var out []string
|
||||
for _, m := range tagRe.FindAllStringSubmatch(rest, -1) {
|
||||
closing, name, attrs, self := m[1] == "/", strings.ToLower(m[2]), m[3], m[4] == "/"
|
||||
if closing {
|
||||
if len(stack) == 0 {
|
||||
return out // the column itself closed
|
||||
}
|
||||
stack = stack[:len(stack)-1]
|
||||
continue
|
||||
}
|
||||
if voidTags[name] || self {
|
||||
continue
|
||||
}
|
||||
level := 0
|
||||
for _, tr := range stack {
|
||||
if !tr {
|
||||
level++
|
||||
}
|
||||
}
|
||||
transparent := false
|
||||
if name == "div" {
|
||||
for _, w := range transparentWrappers {
|
||||
if hasClass(attrs, w) {
|
||||
transparent = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if level == 0 && !transparent {
|
||||
out = append(out, m[0])
|
||||
}
|
||||
stack = append(stack, transparent)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// TestAppPage_OneSectionStyleEverywhere — style A, the operator's choice of 2026-10-16: every section
|
||||
// on the four tabs is an `.app-section` panel, the install page's form groups and progress panel are
|
||||
// the same panel by one CSS rule, and no surface keeps the old flat hairline rule or a box of its own.
|
||||
//
|
||||
// Rendered through the parity cases (every branch the fixtures carry, both the rich and the bare data)
|
||||
// and the production route for the bare harness app.
|
||||
//
|
||||
// RED-PROOF (REPORT.md): leave the logs tab on its old markup (`<div data-tab-panel="logs">`, no
|
||||
// app-section) -> the app_logs case fails. Reverted.
|
||||
func TestAppPage_OneSectionStyleEverywhere(t *testing.T) {
|
||||
s := i18nTestServer(t)
|
||||
tabs := map[string]bool{"app_settings": true, "app_data": true, "app_guide": true, "app_logs": true}
|
||||
checked := map[string]int{}
|
||||
for _, c := range i18nCases() {
|
||||
if !tabs[c.tmpl] {
|
||||
continue
|
||||
}
|
||||
body := renderI18nCase(t, s, "hu", c)
|
||||
if !strings.Contains(body, `class="app-tabs"`) {
|
||||
continue // a not-installed app's guide has no tab bar; the route test below covers it
|
||||
}
|
||||
checked[c.tmpl]++
|
||||
for _, tag := range tabTopLevelBlocks(t, c.name, body) {
|
||||
if hasClass(tag, "app-section") {
|
||||
continue
|
||||
}
|
||||
msg := false
|
||||
for _, mc := range messageClasses {
|
||||
msg = msg || hasClass(tag, mc)
|
||||
}
|
||||
if !msg {
|
||||
t.Errorf("%s: a block outside the section style: %s", c.name, tag)
|
||||
}
|
||||
}
|
||||
for _, legacy := range []string{`class="app-optional-config"`, `class="deploy-storage-info"`, `class="card"`, `class="deploy-container"`, `id="deploy-form"`} {
|
||||
if strings.Contains(body, legacy) {
|
||||
t.Errorf("%s: a tab still carries the old box %s", c.name, legacy)
|
||||
}
|
||||
}
|
||||
}
|
||||
for tmpl := range tabs {
|
||||
if checked[tmpl] == 0 {
|
||||
t.Fatalf("no parity case exercised %s — the check proved nothing for that tab", tmpl)
|
||||
}
|
||||
}
|
||||
|
||||
// A not-installed app: its one view is the guide, without a tab bar — sections after the hero.
|
||||
hs := appPageHarness(t)
|
||||
g := appGet(t, hs, "/apps/grocy").Body.String()
|
||||
g = stripBlockRe.ReplaceAllString(g, "")
|
||||
if i := strings.Index(g, `data-guide-missing="1"`); i < 0 || !strings.Contains(g[strings.LastIndex(g[:i], "<div"):i], "app-section") {
|
||||
t.Error("a not-installed app's guide-missing line is not a section")
|
||||
}
|
||||
|
||||
// The install page: every form group is a .form-section inside the wizard's form, and the
|
||||
// stylesheet draws it with the SAME rule as .app-section.
|
||||
for _, c := range i18nCases() {
|
||||
if c.tmpl != "deploy" {
|
||||
continue
|
||||
}
|
||||
body := renderI18nCase(t, s, "hu", c)
|
||||
form := body[strings.Index(body, `id="deploy-form"`):]
|
||||
form = form[:strings.Index(form, "</form>")]
|
||||
if strings.Count(form, `<h4>`) != strings.Count(form, `<div class="form-section">`) {
|
||||
t.Errorf("%s: a form group on the install page is not a form-section", c.name)
|
||||
}
|
||||
}
|
||||
css, err := os.ReadFile(filepath.Join("templates", "style.css"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
panelRule := regexp.MustCompile(`(?s)\.app-section,\s*\.app-page \.deploy-form \.form-section,\s*\.app-page \.deploy-progress\s*\{([^}]*)\}`).FindSubmatch(css)
|
||||
if panelRule == nil {
|
||||
t.Fatal("style.css: the one panel rule (.app-section + the install page's form-section and progress) is gone")
|
||||
}
|
||||
for _, want := range []string{"background: var(--bg-1)", "border: 1px solid var(--line)", "padding: 18px 24px 22px", "margin: 0 0 16px"} {
|
||||
if !strings.Contains(string(panelRule[1]), want) {
|
||||
t.Errorf("style.css: the panel rule lacks %q (style A)", want)
|
||||
}
|
||||
}
|
||||
// No .app-section rule may redraw it the old way: v0.316.0's flat `padding: 28px 0` + hairline.
|
||||
for _, m := range regexp.MustCompile(`([^{}]*\.app-section[^{}]*)\{([^}]*)\}`).FindAllSubmatch(css, -1) {
|
||||
if strings.Contains(string(m[2]), "padding: 28px 0") {
|
||||
t.Errorf("style.css: %s still carries the old flat section rule", strings.TrimSpace(string(m[1])))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,9 +489,15 @@ func (s *Server) deployPageData(w http.ResponseWriter, r *http.Request, name str
|
||||
if appCfg != nil {
|
||||
for _, n := range appCfg.RestoredLogins {
|
||||
restored[n] = true
|
||||
delete(decryptedEnv, n)
|
||||
}
|
||||
}
|
||||
// v0.319.0: the settings tab's login panel and „Cim es kulcsok" rows, read BEFORE any value
|
||||
// is dropped below — they learn only whether a value is stored, never the secret itself.
|
||||
data["LoginFields"] = appLoginFields(meta, decryptedEnv, restored)
|
||||
data["SettingsRows"] = appSettingRows(meta, decryptedEnv, s.msgLang(lang, "deploy.igen"), s.msgLang(lang, "app_page.value_no"))
|
||||
for n := range restored {
|
||||
delete(decryptedEnv, n)
|
||||
}
|
||||
// R-709 (v0.280.0): a `type: password` value is never written into this page's HTML either — like a
|
||||
// `type: secret` (R-254) it is fetched on demand (/stacks/<n>/auto-field/reveal).
|
||||
for _, f := range meta.DeployFields {
|
||||
@@ -578,6 +584,7 @@ func (s *Server) deployPageData(w http.ResponseWriter, r *http.Request, name str
|
||||
for _, dp := range deployPaths {
|
||||
if dp.Path == hdd {
|
||||
inList = true
|
||||
data["CurrentHDDLabel"] = dp.Label // v0.319.0: the settings tab's „Tarhely" row
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2626,7 +2633,7 @@ func (s *Server) appAutoFieldRevealHandler(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
val := crypto.DecryptMap(s.encKey, appCfg.Env)[envVar]
|
||||
if strings.TrimSpace(val) == "" {
|
||||
if !storedValuePresent(val) { // the settings tab reads the same function, so it never offers this 404
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
escrowJSON(w, http.StatusNotFound, nil, s.msg(r, "escrow.no_stored_value"))
|
||||
return
|
||||
|
||||
@@ -187,6 +187,18 @@ func i18nCasesA() []i18nCase {
|
||||
{"deploy_new_memory_blocked", "deploy", func() map[string]interface{} {
|
||||
return i18nDeployData(false, stacks.StateNotDeployed, 3)
|
||||
}},
|
||||
// v0.319.0: `/apps/{slug}/install` opened for an app that IS installed — the form read-only, its
|
||||
// secrets fetched on demand. The settings tab stopped rendering this form, so these two cases are
|
||||
// what renders its installed-app branches now.
|
||||
{"deploy_installed", "deploy", func() map[string]interface{} {
|
||||
return i18nDeployData(true, stacks.StateRunning, 1)
|
||||
}},
|
||||
{"deploy_installed_restored", "deploy", func() map[string]interface{} {
|
||||
d := i18nDeployData(true, stacks.StateRunning, 1)
|
||||
d["DeployedFieldValues"] = map[string]string{"SUBDOMAIN": "paste"}
|
||||
d["RestoredLogins"] = map[string]bool{"ADMIN_PASSWORD": true}
|
||||
return d
|
||||
}},
|
||||
{"app_settings_running", "app_settings", func() map[string]interface{} {
|
||||
return i18nDeployData(true, stacks.StateRunning, 1)
|
||||
}},
|
||||
@@ -198,6 +210,26 @@ func i18nCasesA() []i18nCase {
|
||||
d := i18nDeployData(true, stacks.StateRunning, 1)
|
||||
d["DeployedFieldValues"] = map[string]string{"SUBDOMAIN": "paste"}
|
||||
d["RestoredLogins"] = map[string]bool{"ADMIN_PASSWORD": true}
|
||||
d["LoginFields"] = []AppLoginField{{EnvVar: "ADMIN_PASSWORD", Label: "Admin jelszó mező", OnDemand: true, Stored: true, Restored: true}}
|
||||
return d
|
||||
}},
|
||||
// v0.319.0: the box holds no login value (Calibre-Web installed before its template had one) —
|
||||
// a sentence for each half, never an eye that answers 404.
|
||||
{"app_settings_login_not_stored", "app_settings", func() map[string]interface{} {
|
||||
d := i18nDeployData(true, stacks.StateRunning, 1)
|
||||
d["LoginFields"] = []AppLoginField{
|
||||
{EnvVar: "ADMIN_USER", Label: "Admin név mező", IsName: true, OnDemand: true},
|
||||
{EnvVar: "ADMIN_PASSWORD", Label: "Admin jelszó mező", OnDemand: true},
|
||||
}
|
||||
return d
|
||||
}},
|
||||
// v0.319.0: a plain-text login name (Nextcloud, Paperless) is shown as text beside the password.
|
||||
{"app_settings_login_text_name", "app_settings", func() map[string]interface{} {
|
||||
d := i18nDeployData(true, stacks.StateRunning, 2)
|
||||
d["LoginFields"] = []AppLoginField{
|
||||
{EnvVar: "PAPERLESS_ADMIN_USER", Label: "Admin név mező", IsName: true, Value: "konyveles", Stored: true},
|
||||
{EnvVar: "PAPERLESS_ADMIN_PASSWORD", Label: "Admin jelszó mező", OnDemand: true, Stored: true},
|
||||
}
|
||||
return d
|
||||
}},
|
||||
{"settings_system_full", "settings_system", func() map[string]interface{} {
|
||||
@@ -399,5 +431,18 @@ func i18nDeployData(deployed bool, st stacks.ContainerState, variant int) map[st
|
||||
d["CurrentHDDPathMissing"] = true
|
||||
d["StoragePaths"] = nil
|
||||
}
|
||||
// v0.319.0: the settings tab's login panel and „Cim es kulcsok" rows (app_login.go). The install
|
||||
// page reads neither key, so its fixtures do not move.
|
||||
if deployed {
|
||||
d["CurrentHDDLabel"] = "HDD egy"
|
||||
d["LoginFields"] = []AppLoginField{
|
||||
{EnvVar: "ADMIN_USER", Label: "Admin név mező", IsName: true, OnDemand: true, Stored: true},
|
||||
{EnvVar: "ADMIN_PASSWORD", Label: "Admin jelszó mező", OnDemand: true, Stored: true},
|
||||
}
|
||||
d["SettingsRows"] = []AppSettingRow{
|
||||
{EnvVar: "SECRET_KEY", Label: "Titkos kulcs érték", OnDemand: true},
|
||||
{EnvVar: "LANG_SEL", Label: "Felület nyelve", Value: "Magyar nyelv"},
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -115,9 +115,11 @@ func TestAppInfo_HeroPlaceholder_ScreenshotsUntouched(t *testing.T) {
|
||||
if !strings.Contains(html, canonical3Step) {
|
||||
t.Error("app_info hero logo must use the canonical 3-step chain")
|
||||
}
|
||||
// Screenshots must be byte-identical to before — they still vanish on error, no placeholder.
|
||||
if !strings.Contains(html, `class="app-screenshot"`) || !strings.Contains(html, `onerror="this.style.display='none'"`) {
|
||||
t.Error("app_info screenshot imgs must keep their original vanish-on-error onerror")
|
||||
// Screenshots still vanish on error, no placeholder. v0.319.0: through appShotGone, which also
|
||||
// hides the screenshots panel when the last one is gone — a panel around nothing is not drawn.
|
||||
if !strings.Contains(html, `class="app-screenshot"`) || !strings.Contains(html, `onerror="appShotGone(this)"`) ||
|
||||
!strings.Contains(html, "img.style.display = 'none';") {
|
||||
t.Error("app_info screenshot imgs must vanish on error")
|
||||
}
|
||||
if strings.Contains(html, `class="app-screenshot" `) && strings.Contains(html, `app-screenshot" data-fallback`) {
|
||||
t.Error("app_info screenshots must NOT gain a placeholder fallback")
|
||||
|
||||
@@ -173,22 +173,11 @@ deploy_fields:
|
||||
return s
|
||||
}
|
||||
|
||||
// optionTag returns the full <option ...>...</option> block whose value attribute equals path.
|
||||
func optionTag(t *testing.T, body, path string) string {
|
||||
t.Helper()
|
||||
marker := `value="` + path + `"`
|
||||
i := strings.Index(body, marker)
|
||||
if i < 0 {
|
||||
t.Fatalf("no option with value %q in rendered page", path)
|
||||
}
|
||||
start := strings.LastIndex(body[:i], "<option")
|
||||
end := strings.Index(body[i:], ">")
|
||||
return body[start : i+end]
|
||||
}
|
||||
|
||||
// Deployed on the NAS → the NAS option carries `selected`; the default drive does NOT.
|
||||
// Companion red-proof: revert deploy.html to the IsDefault-only selection → the default option is
|
||||
// selected instead → both assertions FAIL (the exact S-C lie from the RCA).
|
||||
// Deployed on the NAS → the „Tarhely" row names the NAS, never the default drive.
|
||||
// v0.319.0: the settings tab shows the storage as a row, not a disabled select — the RCA fix 4 rule is
|
||||
// unchanged: the view states the STORED HDD_PATH.
|
||||
// Companion red-proof: make the row read the default drive's label → the first assertion FAILS (the exact
|
||||
// S-C lie from the RCA).
|
||||
func TestDeployPage_DeployedSelectShowsStoredHDDPath(t *testing.T) {
|
||||
s := testDeployPageServer(t, "/mnt/felhom-drives/nas-media")
|
||||
// v0.315.0: an installed app's fields are the app page's settings TAB. The old /stacks/…/deploy
|
||||
@@ -197,28 +186,34 @@ func TestDeployPage_DeployedSelectShowsStoredHDDPath(t *testing.T) {
|
||||
if rec.Code != 200 {
|
||||
t.Fatalf("GET settings tab = %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if nas := optionTag(t, body, "/mnt/felhom-drives/nas-media"); !strings.Contains(nas, "selected") {
|
||||
t.Errorf("the STORED path's option must be selected, got: %s", nas)
|
||||
row := storageRow(t, rec.Body.String())
|
||||
if !strings.Contains(row, "nas-media") {
|
||||
t.Errorf("the storage row must name the STORED path's drive, got: %s", row)
|
||||
}
|
||||
if usb := optionTag(t, body, "/mnt/felhom-drives/felhom-usb"); strings.Contains(usb, "selected") {
|
||||
t.Errorf("the default drive must NOT be selected for a deployed app, got: %s", usb)
|
||||
if strings.Contains(row, "felhom-usb") {
|
||||
t.Errorf("the storage row must NOT name the default drive for a deployed app, got: %s", row)
|
||||
}
|
||||
}
|
||||
|
||||
// Stored path absent from the schedulable list → an extra disabled option names it verbatim (the
|
||||
// view must never silently show a different storage than app.yaml).
|
||||
// storageRow returns the settings tab's „Tarhely" value cell.
|
||||
func storageRow(t *testing.T, body string) string {
|
||||
t.Helper()
|
||||
i := strings.Index(body, `data-app-storage-row="1"`)
|
||||
if i < 0 {
|
||||
t.Fatalf("no storage row in the rendered settings tab")
|
||||
}
|
||||
return body[i : i+strings.Index(body[i:], "</div>")]
|
||||
}
|
||||
|
||||
// Stored path absent from the schedulable list → the row names it verbatim as not available (the view
|
||||
// must never silently show a different storage than app.yaml).
|
||||
func TestDeployPage_MissingStoredPathRendersTruthOption(t *testing.T) {
|
||||
s := testDeployPageServer(t, "/mnt/felhom-drives/gone")
|
||||
rec := getPage(t, s, "/apps/testapp/settings")
|
||||
if rec.Code != 200 {
|
||||
t.Fatalf("GET settings tab = %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
body := rec.Body.String()
|
||||
if !strings.Contains(body, "/mnt/felhom-drives/gone (nem elérhető)") {
|
||||
t.Errorf("missing stored path must render as a disabled truth option")
|
||||
}
|
||||
if gone := optionTag(t, body, "/mnt/felhom-drives/gone"); !strings.Contains(gone, "selected") || !strings.Contains(gone, "disabled") {
|
||||
t.Errorf("the truth option must be selected+disabled, got: %s", gone)
|
||||
if row := storageRow(t, rec.Body.String()); !strings.Contains(row, "/mnt/felhom-drives/gone (nem elérhető)") {
|
||||
t.Errorf("a missing stored path must render as its own path, not available; got: %s", row)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
{{if .FlashError}}<div class="monitoring-banner monitoring-banner-red">{{.FlashError}}</div>{{end}}
|
||||
|
||||
{{if .DataPathCards}}
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.hova_tegyem_a_fajlokat"}}</h3>
|
||||
<p class="form-hint">{{T "app_info.ezeket_a_mappakat_a_fajlkezeloben"}}</p>
|
||||
<div class="datapath-list">
|
||||
@@ -36,7 +36,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if and .Stack.Deployed .MigrateTargets}}
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.athelyezes_masik_tarhelyre"}}</h3>
|
||||
<p class="form-hint">{{T "app_info.ennek_az_alkalmazasnak_az_adatait"}}</p>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
|
||||
@@ -87,7 +87,7 @@ function appMigrate(btn,app,label){
|
||||
|
||||
{{/* The nightly copy, stated as a fact. It was a card on the settings page; the files it is about
|
||||
live here. */}}
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>{{T "deploy.biztonsagi_mentes"}}</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
@@ -255,13 +255,13 @@ function appMigrate(btn,app,label){
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="progressCard" class="card" style="max-width:700px;display:none">
|
||||
<div id="progressCard" class="app-section" style="display:none">
|
||||
<h3>{{T "app_export.folyamat"}}</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">
|
||||
<div id="doneCard" class="app-section" style="display:none">
|
||||
<h3 style="color:var(--blue)">{{T "app_export.kesz"}}</h3>
|
||||
<div style="margin-bottom:1rem">
|
||||
<span id="doneFile" style="font-weight:600"></span>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
{{/* THE APP'S CONFIGURATION FIELDS — shared by the install wizard and the settings tab.
|
||||
{{/* THE APP'S CONFIGURATION FIELDS — the install wizard's form (deploy.html).
|
||||
|
||||
ONE copy of this markup, rendered by two surfaces, because they are the same fields: the
|
||||
install page submits them, the settings tab shows what was submitted. `.AlreadyDeployed` is
|
||||
the single flag every branch below reads, exactly as it did when both surfaces were one page —
|
||||
that is what keeps the install form and the settings view from drifting apart.
|
||||
v0.319.0: the settings tab no longer renders this — it shows the install answers as rows and
|
||||
the login as one panel (app_settings.html, app_login.go). The `.AlreadyDeployed` branches below
|
||||
are NOT dead: `/apps/{slug}/install` still answers for an app that is installed, and renders
|
||||
this form read-only. Their guards are what keep an installed app's secrets out of that page
|
||||
(TestDeployPage_DeployedApp_DoesNotCarryTheSecret, TestR709_AnInstalledAppsPasswordIsNotInThePage)
|
||||
and the parity case `deploy_installed` renders them.
|
||||
|
||||
The secret rules are unchanged and load-bearing:
|
||||
R-254 — an INSTALLED app's `type: secret` value is not in the page; the eye fetches it.
|
||||
|
||||
@@ -14,21 +14,33 @@
|
||||
Everything here is PLAIN TEXT through html/template. No markdown, no raw HTML: a step that
|
||||
contains a tag is shown as the characters the author typed. */}}
|
||||
|
||||
<!-- Screenshots (graceful — hidden if assets don't exist) -->
|
||||
<script>
|
||||
// Defined BEFORE the images: an error event can fire as soon as an <img> is parsed.
|
||||
function appShotGone(img) {
|
||||
img.style.display = 'none';
|
||||
var box = img.parentNode, left = false;
|
||||
box.querySelectorAll('img').forEach(function (i) { if (i.style.display !== 'none') { left = true; } });
|
||||
if (!left) { box.closest('.app-section').style.display = 'none'; }
|
||||
}
|
||||
</script>
|
||||
<!-- Screenshots (graceful — each hidden if its asset does not exist, and the section with the last
|
||||
one, so a panel is never drawn around nothing) -->
|
||||
<div class="app-section" data-screenshots-section="1">
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="{{screenshotURL .Meta.Slug 1}}" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="{{screenshotURL .Meta.Slug 2}}" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="{{screenshotURL .Meta.Slug 3}}" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if not .Deployed}}
|
||||
{{/* The install button lives on this tab, because this tab IS the page for a not-installed app.
|
||||
The server refuses a non-installable template regardless; hiding the button keeps the page
|
||||
from offering an action that would be rejected. */}}
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
{{if canInstall .Meta}}
|
||||
<a href="/apps/{{.Meta.Slug}}/install" class="btn btn-primary" data-install-cta="1"
|
||||
onclick="return checkBeforeDeploy(event, '{{.Stack.Name}}')">{{T "app_info.telepites"}}</a>
|
||||
@@ -71,7 +83,7 @@
|
||||
{{if .AppInfo.Guide.Rendered}}
|
||||
<div class="app-guide" data-guide="1" data-guide-checked="{{.AppInfo.Guide.CheckedVersion}}">
|
||||
{{- range .AppInfo.Guide.Sections}}
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<div class="app-section" data-guide-section="1">
|
||||
<h3>{{.Title}}</h3>
|
||||
{{if .Intro}}<p>{{.Intro}}</p>{{end}}
|
||||
{{if .Steps}}
|
||||
@@ -82,7 +94,7 @@
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .AppInfo.Guide.FAQ}}
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-faq="1">
|
||||
<div class="app-section" data-guide-faq="1">
|
||||
<h3>{{T "app_page.faq"}}</h3>
|
||||
<dl class="app-faq">
|
||||
{{- range .AppInfo.Guide.FAQ}}
|
||||
@@ -94,7 +106,7 @@
|
||||
{{- end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="form-hint" style="margin-top:1rem" data-guide-missing="1">{{T "app_page.guide_missing"}}</p>
|
||||
<div class="app-section" data-guide-missing="1"><p>{{T "app_page.guide_missing"}}</p></div>
|
||||
{{end}}
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
the reason the tabs are four URLs and not four hidden panels: a poller on the settings tab
|
||||
would ask the box for 200 log lines every three seconds for nothing. */}}
|
||||
|
||||
<div data-tab-panel="logs">
|
||||
<div class="app-section" data-tab-panel="logs">
|
||||
<div class="logs-container" id="logs-container">
|
||||
<pre class="logs-output" id="logs-output">{{.Logs}}</pre>
|
||||
</div>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
„Adatok es mentes" holds everything about the FILES, including the backup card that used to
|
||||
sit on this page. */}}
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
{{if .FlashSuccess}}<div class="flash flash-success">{{.FlashSuccess}}</div>{{end}}
|
||||
{{if .FlashError}}<div class="flash flash-error">{{.FlashError}}</div>{{end}}
|
||||
|
||||
@@ -62,13 +62,13 @@
|
||||
|
||||
{{/* 3. THE REST OF WHAT THE APP WANTS FROM THE HOUSEHOLD. */}}
|
||||
{{- if .InstallHoldClosed}}
|
||||
<div class="app-section" style="margin-top:1rem" id="install-hold-card">
|
||||
<div class="app-section" id="install-hold-card">
|
||||
<h3>{{T "app_info.install_hold_title"}}</h3>
|
||||
<p>{{T "app_info.install_hold_closed"}}</p>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .SetupGateClosed}}
|
||||
<div class="app-section" style="margin-top:1rem" id="setup-gate-card">
|
||||
<div class="app-section" id="setup-gate-card">
|
||||
<h3>{{T "app_info.setup_gate_title"}}</h3>
|
||||
<p>{{T "app_info.setup_gate_closed"}}</p>{{- if .SignupNative}}
|
||||
<p class="form-hint" data-restarts-once="true">{{T "app_info.restarts_once"}}</p>{{- end}}
|
||||
@@ -84,7 +84,7 @@
|
||||
{{- end}}
|
||||
|
||||
{{- if .CloseSignupOffered}}
|
||||
<div class="app-section" style="margin-top:1rem" id="close-signup-card">
|
||||
<div class="app-section" id="close-signup-card">
|
||||
<h3>{{T "app_info.signup_title"}}</h3>
|
||||
<p>{{T "app_info.close_signup_text"}}</p>{{- if .SignupNative}}
|
||||
<p class="form-hint" data-restarts-once="true">{{T "app_info.restarts_once"}}</p>{{- end}}
|
||||
@@ -93,7 +93,7 @@
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if or .SignupClosed .SignupOpenUntil}}
|
||||
<div class="app-section" style="margin-top:1rem" id="signup-card">
|
||||
<div class="app-section" id="signup-card">
|
||||
<h3>{{T "app_info.signup_title"}}</h3>
|
||||
{{- if .SignupOpenUntil}}
|
||||
<p>{{T "app_info.signup_open_until"}} {{.SignupOpenUntil}}</p>
|
||||
@@ -121,6 +121,35 @@
|
||||
|
||||
{{/* The logins, each already its own guarded card on the details page. */}}
|
||||
<div class="app-sections">
|
||||
{{/* v0.319.0 — „BELEPES AZ ALKALMAZASBA": the name and the password of the app's first login,
|
||||
side by side. The values are fetched on demand (R-254, R-709); the page learns only whether
|
||||
the box holds one (app_login.go), and a field it holds none for is a sentence, not an eye. */}}
|
||||
{{if .LoginFields}}
|
||||
<div class="app-section" id="app-login-panel" data-login-panel="1">
|
||||
<h3>{{T "app_page.login_title"}}</h3>
|
||||
<p>{{T "app_page.login_desc"}}</p>
|
||||
<div class="app-login-pair">
|
||||
{{- range .LoginFields}}
|
||||
<div class="app-login-field" data-login-field="{{.EnvVar}}">
|
||||
<label class="app-login-label" for="field-{{.EnvVar}}">{{.Label}}</label>
|
||||
{{- if .Restored}}
|
||||
<p class="form-hint" data-login-restored="{{.EnvVar}}">{{T "deploy.login_from_backup"}}</p>
|
||||
{{- else if not .Stored}}
|
||||
<p class="form-hint" data-login-not-stored="{{.EnvVar}}">{{if .IsName}}{{T "app_page.login_no_name"}}{{else}}{{T "app_page.login_no_password"}}{{end}}</p>
|
||||
{{- else if .OnDemand}}
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-{{.EnvVar}}" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-login-reveal="{{.EnvVar}}" onclick="revealAutoField('{{$.Stack.Name}}','{{.EnvVar}}', this, 'field-{{.EnvVar}}')">{{T "deploy.megjelenites"}}</button>
|
||||
</div>
|
||||
{{- else}}
|
||||
<input type="text" id="field-{{.EnvVar}}" class="form-control" value="{{.Value}}" readonly>
|
||||
{{- end}}
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .InitialCreds}}
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.kezdeti_belepesi_adatok"}}</h3>
|
||||
@@ -173,8 +202,8 @@
|
||||
household reached by pressing „Beallitasok" on an installed app. The hint lives with the
|
||||
fields it is about, at the bottom of this tab. */}}
|
||||
{{if .StorageInfo}}
|
||||
<div class="deploy-storage-info">
|
||||
<h4>{{T "deploy.adattarolas"}}</h4>
|
||||
<div class="app-section">
|
||||
<h3>{{T "deploy.adattarolas"}}</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">{{T "deploy.tarhely"}}</span>
|
||||
@@ -201,8 +230,8 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .StaleData}}
|
||||
<div class="deploy-stale-data">
|
||||
<h4>{{T "deploy.korabbi_adatok"}}</h4>
|
||||
<div class="app-section deploy-stale-data">
|
||||
<h3>{{T "deploy.korabbi_adatok"}}</h3>
|
||||
<p class="form-hint" style="margin-bottom:1rem">
|
||||
{{T "deploy.az_alkalmazas_adatainak_masolata_megtala"}}
|
||||
</p>
|
||||
@@ -230,7 +259,7 @@
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .HasIntegrations}}
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>{{T "deploy.integraciok"}}</h3>
|
||||
<p class="config-group-desc">
|
||||
{{T "deploy.mas_telepitett_alkalmazasokkal_valo_ossz"}}
|
||||
@@ -290,7 +319,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if .GeoGlobalEnabled}}
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>{{T "deploy.foldrajzi_korlatozas"}}</h3>
|
||||
<p class="config-group-desc">
|
||||
{{T "deploy.az_alkalmazas_egyeni_orszagkorlatozas_ne"}}
|
||||
@@ -425,7 +454,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if .HasOptionalConfig}}
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>{{T "deploy.opcionalis_beallitasok"}}</h3>
|
||||
{{range .OptionalConfig}}
|
||||
<div class="config-group">
|
||||
@@ -519,7 +548,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if .AppEmailSupported}}
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>{{T "deploy.email_kuldes"}}</h3>
|
||||
<p class="config-group-desc">
|
||||
{{T "deploy.ez_az_alkalmazas_tud_emailt"}}
|
||||
@@ -542,18 +571,38 @@
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="app-section">
|
||||
{{/* „CIM ES KULCSOK" (v0.319.0) — what was set at install, as rows. The hint that says they
|
||||
cannot be changed sits under the title it is about, not at the bottom of the tab. The domain,
|
||||
the subdomain and the data folder are the „Cim" and „Tarhely" rows; the login is the panel
|
||||
above; everything else is a row here (app_login.go appSettingRows). */}}
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>{{T "app_page.address_and_keys"}}</h3>
|
||||
<p class="app-field-hint" data-readonly-hint="1">{{T "app_page.installed_readonly"}}</p>
|
||||
{{if .EffectiveSubdomain}}
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">{{T "app_page.address"}}</div>
|
||||
<div class="app-field-value" data-app-address-row="1">{{.EffectiveSubdomain}}.{{.Domain}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
{{template "app_deploy_fields" .}}
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">{{T "app_page.installed_readonly"}}</p>
|
||||
{{if .CurrentHDDPath}}
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">{{T "deploy.tarhely"}}</div>
|
||||
<div class="app-field-value" data-app-storage-row="1">{{if .CurrentHDDPathMissing}}{{T "deploy.nem_elerheto"}}{{else if .CurrentHDDLabel}}{{.CurrentHDDLabel}}{{else}}<span class="mono">{{.CurrentHDDPath}}</span>{{end}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- range .SettingsRows}}
|
||||
<div class="app-field-row" data-setting-row="{{.EnvVar}}">
|
||||
<div class="app-field-label">{{.Label}}</div>
|
||||
{{- if .OnDemand}}
|
||||
<div class="app-field-value input-with-button">
|
||||
<input type="password" id="auto-field-{{.EnvVar}}" class="form-control" value="" placeholder="••••••••••••" readonly aria-label="{{.Label}}">
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('{{$.Stack.Name}}','{{.EnvVar}}', this)">{{T "deploy.megjelenites"}}</button>
|
||||
</div>
|
||||
{{- else}}
|
||||
<div class="app-field-value">{{.Value}}</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -612,46 +661,9 @@ function icCopyPw(btn) {
|
||||
|
||||
{{template "app_press_script" .}}
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
// Check on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
// R-254 site two: on an already-deployed app the value is NOT in this page — it is fetched on
|
||||
// demand, and the server records the act. Nothing caches it between presses.
|
||||
// R-709 (v0.280.0): an installed app's `type: password` value, fetched on demand like a secret.
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || '{{T "deploy.a_lekeres_nem_sikerult"}}'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('{{T "deploy.a_lekeres_nem_sikerult"}}'); });
|
||||
}
|
||||
// R-254 site two: on an already-deployed app no secret is in this page — it is fetched on demand,
|
||||
// and the server records the act. Nothing caches it between presses. Since v0.319.0 the login
|
||||
// panel's name and password use this too (R-709: a `type: password` value, fetched like a secret).
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
@@ -1300,13 +1300,17 @@ a.stat-card:hover {
|
||||
.login-footer a:hover { color: var(--blue); }
|
||||
|
||||
/* --- App Info Page --- */
|
||||
/* ── THE APP PAGE (v0.316.0) — one column, hairlines, boxes only where they mean something ───────
|
||||
Operator-approved mockup, 2026-10-15. The rules, and why each one:
|
||||
ONE COLUMN of ONE WIDTH. Boxes of different widths read as a page assembled by accident.
|
||||
SECTIONS ARE SEPARATED BY HAIRLINES, not drawn as cards. A card says "this is a thing you act
|
||||
on"; most of an app page is a thing you READ, so a card there is a promise it cannot keep.
|
||||
A BOX MEANS SOMETHING. Exactly two exist: blue when the app is waiting for the household, amber
|
||||
when something is risky. The exception-colour principle, applied to shape as well as colour.
|
||||
/* ── THE APP PAGE (v0.319.0) — one column, the header, the tab bar and every section a panel ─────
|
||||
Operator-approved mockup „Felhom app oldal — egy oszlop", 2026-10-16, section style A. The rules:
|
||||
ONE COLUMN of ONE WIDTH. Every panel is the column's full width; panels of different widths
|
||||
read as a page assembled by accident.
|
||||
PANELS ON --bg-1. v0.316.0's hairline sections sat on the page background with the header and
|
||||
the tab bar, and the operator found the page flat: nothing told one section from the next.
|
||||
Now the hero, the tab bar and each section are their own panel, 16px apart.
|
||||
ONE SECTION STYLE ON EVERY SURFACE — the four tabs and the install page. Never mixed. Pinned by
|
||||
TestAppPage_OneSectionStyleEverywhere.
|
||||
COLOUR STILL MEANS SOMETHING. A panel is neutral; blue is the app waiting for the household,
|
||||
amber is a risk. The callouts keep their colours inside or as a panel.
|
||||
Tokens only — no raw hexes, 2px radius, no shadows. */
|
||||
|
||||
.app-page {
|
||||
@@ -1330,18 +1334,23 @@ a.stat-card:hover {
|
||||
}
|
||||
.app-back:hover { color: var(--text-1); }
|
||||
|
||||
/* HERO — no box, no border. The app's identity is a heading, not a card. */
|
||||
/* HERO — its own panel. */
|
||||
.app-hero {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 20px 24px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.app-hero-logo {
|
||||
width: 64px; height: 64px;
|
||||
min-width: 64px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--bg-1);
|
||||
background: var(--bg-2);
|
||||
border-radius: var(--radius);
|
||||
object-fit: contain;
|
||||
padding: 8px;
|
||||
@@ -1364,40 +1373,62 @@ a.stat-card:hover {
|
||||
worked. A success that needed no decision is not news; it is a date. */
|
||||
.app-meta { font-size: 12px; color: var(--text-3); }
|
||||
|
||||
/* TABS — links with a line under the open one. No pills, no fills. */
|
||||
/* TABS — a panel; the open tab is filled and underlined. */
|
||||
.app-tabs {
|
||||
display: flex;
|
||||
gap: 28px;
|
||||
gap: 4px;
|
||||
flex-wrap: wrap;
|
||||
margin: 28px 0 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 4px;
|
||||
margin: 16px 0 20px;
|
||||
}
|
||||
.app-tab {
|
||||
font-size: 15px; font-weight: 600;
|
||||
padding: 12px 0;
|
||||
padding: 10px 18px;
|
||||
color: var(--text-2);
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.app-tab:hover { color: var(--text-1); }
|
||||
.app-tab-active { color: var(--text-1); border-bottom-color: var(--blue-bright); }
|
||||
.app-tab-active { color: var(--text-1); background: var(--bg-2); border-bottom-color: var(--blue-bright); }
|
||||
|
||||
/* SECTIONS — a hairline between, nothing around. */
|
||||
.app-section {
|
||||
padding: 28px 0;
|
||||
border-bottom: 1px solid var(--line-soft);
|
||||
background: none;
|
||||
border-left: 0; border-right: 0; border-top: 0;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
/* SECTIONS — style A: each its own panel, the column's width, 16px apart, the title 18px from the
|
||||
top edge with a soft rule under it. The install page's form groups (.form-section inside the
|
||||
wizard's form) and its progress panel are the same panel: one style on every surface. */
|
||||
.app-section,
|
||||
.app-page .deploy-form .form-section,
|
||||
.app-page .deploy-progress {
|
||||
padding: 18px 24px 22px;
|
||||
background: var(--bg-1);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
.app-section:last-child { border-bottom: 0; }
|
||||
.app-section > h3, .app-section > h4 {
|
||||
font-size: 17px; font-weight: 700; color: var(--text-1); margin: 0 0 6px;
|
||||
.app-section > h3:first-child, .app-section > h4:first-child,
|
||||
.app-page .deploy-form .form-section > h4:first-child,
|
||||
.app-page .deploy-progress > h3:first-child {
|
||||
font-size: 17px; font-weight: 700; color: var(--text-1);
|
||||
margin: 0 0 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--line-soft);
|
||||
}
|
||||
/* A heading further down a panel is a sub-heading, not a second title. */
|
||||
.app-section > h3:not(:first-child), .app-section > h4:not(:first-child) {
|
||||
font-size: 14px; font-weight: 600; color: var(--text-1); margin: 16px 0 6px;
|
||||
}
|
||||
.app-section > p { font-size: 14px; color: var(--text-2); line-height: 1.55; margin: 0 0 16px; }
|
||||
.app-section > :last-child { margin-bottom: 0; }
|
||||
/* The install wizard's form was one box around all its groups; the groups are the panels now. */
|
||||
.app-page .deploy-form { background: none; border: 0; padding: 0; }
|
||||
|
||||
/* THE LOGIN PAIR — the name and the password side by side, stacked on a phone. */
|
||||
.app-login-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
||||
.app-login-field { min-width: 0; }
|
||||
.app-login-label { display: block; font-size: 13px; color: var(--text-2); margin-bottom: 6px; }
|
||||
|
||||
/* ROWS — label and value, one grid so every value on the page starts at the same x. */
|
||||
.app-field-row {
|
||||
@@ -1418,17 +1449,13 @@ a.stat-card:hover {
|
||||
font-size: 14px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.app-callout > h3, .app-callout > h4 { font-size: 15px; font-weight: 700; margin: 0 0 6px; color: var(--text-1); }
|
||||
.app-callout > h3, .app-callout > h4 { font-size: 15px; font-weight: 700; margin: 0 0 6px; color: var(--text-1); padding-bottom: 0; border-bottom: 0; }
|
||||
/* „The app is waiting for you" — a request, never a fault, so blue and never amber. */
|
||||
.app-callout-ask { border-color: var(--blue); background: var(--blue-dim); }
|
||||
/* „Something here is risky" — the exception colour, and only on a deviation. */
|
||||
.app-callout-warn { border-color: var(--warn); background: var(--warn-dim); }
|
||||
|
||||
/* The settings tab reuses the install wizard's field markup, which carries that page's boxes: a
|
||||
card around the whole form and one around every generated value. On a TAB those are sections, so
|
||||
the boxes come off — the input itself is still a field, which is what tells the household it is a
|
||||
value. Scoped to .app-section, so the install page (same column, its own form) is untouched. */
|
||||
.app-section .deploy-form,
|
||||
/* A box inside a panel would be a narrower inner card; the panel is the box. */
|
||||
.app-section .form-group-auto,
|
||||
.app-section .app-optional-config,
|
||||
.app-section .deploy-storage-info,
|
||||
@@ -1450,8 +1477,9 @@ a.stat-card:hover {
|
||||
.app-page { padding: 0 0 24px; }
|
||||
.app-field-row { grid-template-columns: 1fr; gap: 4px; }
|
||||
.app-hero-actions { margin-left: 0; flex-basis: 100%; }
|
||||
.app-tabs { gap: 18px; }
|
||||
.app-tab { font-size: 14px; }
|
||||
.app-tab { font-size: 14px; padding: 8px 12px; }
|
||||
.app-hero, .app-section, .app-page .deploy-form .form-section, .app-page .deploy-progress { padding-left: 16px; padding-right: 16px; }
|
||||
.app-login-pair { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.app-info-hero {
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
<h3>Hova tegyem a fájlokat?</h3>
|
||||
<p class="form-hint">Ezeket a mappákat a Fájlkezelőben éred el. Előfordulhat, hogy először be kell jelentkezned a Fájlkezelőbe.</p>
|
||||
<div class="datapath-list">
|
||||
@@ -241,7 +241,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
<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">
|
||||
@@ -291,7 +291,7 @@ function appMigrate(btn,app,label){
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
@@ -429,13 +429,13 @@ function appMigrate(btn,app,label){
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="progressCard" class="card" style="max-width:700px;display:none">
|
||||
<div id="progressCard" class="app-section" style="display:none">
|
||||
<h3>Folyamat</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">
|
||||
<div id="doneCard" class="app-section" style="display:none">
|
||||
<h3 style="color:var(--blue)">Kész!</h3>
|
||||
<div style="margin-bottom:1rem">
|
||||
<span id="doneFile" style="font-weight:600"></span>
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
@@ -309,13 +309,13 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="progressCard" class="card" style="max-width:700px;display:none">
|
||||
<div id="progressCard" class="app-section" style="display:none">
|
||||
<h3>Folyamat</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">
|
||||
<div id="doneCard" class="app-section" style="display:none">
|
||||
<h3 style="color:var(--blue)">Kész!</h3>
|
||||
<div style="margin-bottom:1rem">
|
||||
<span id="doneFile" style="font-weight:600"></span>
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section deploy-cross-drive">
|
||||
<div class="app-section">
|
||||
<h3>Biztonsági mentés</h3>
|
||||
|
||||
<div class="cross-drive-nightly">
|
||||
|
||||
@@ -207,19 +207,30 @@
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
function appShotGone(img) {
|
||||
img.style.display = 'none';
|
||||
var box = img.parentNode, left = false;
|
||||
box.querySelectorAll('img').forEach(function (i) { if (i.style.display !== 'none') { left = true; } });
|
||||
if (!left) { box.closest('.app-section').style.display = 'none'; }
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app-section" data-screenshots-section="1">
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/privatebin-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/privatebin-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/privatebin-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
|
||||
<p class="form-hint" data-not-installable="1">Ez az alkalmazás most nem telepíthető.</p>
|
||||
|
||||
@@ -236,7 +247,7 @@
|
||||
|
||||
|
||||
|
||||
<p class="form-hint" style="margin-top:1rem" data-guide-missing="1">Ehhez az alkalmazáshoz még nem készült útmutató.</p>
|
||||
<div class="app-section" data-guide-missing="1"><p>Ehhez az alkalmazáshoz még nem készült útmutató.</p></div>
|
||||
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
@@ -218,14 +218,25 @@
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
function appShotGone(img) {
|
||||
img.style.display = 'none';
|
||||
var box = img.parentNode, left = false;
|
||||
box.querySelectorAll('img').forEach(function (i) { if (i.style.display !== 'none') { left = true; } });
|
||||
if (!left) { box.closest('.app-section').style.display = 'none'; }
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app-section" data-screenshots-section="1">
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/privatebin-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/privatebin-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/privatebin-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -262,7 +273,7 @@
|
||||
|
||||
|
||||
<div class="app-guide" data-guide="1" data-guide-checked="2.0.6">
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<div class="app-section" data-guide-section="1">
|
||||
<h3>Első bekapcsolás</h3>
|
||||
<p>Nyisd meg a böngészőben.</p>
|
||||
|
||||
@@ -270,7 +281,7 @@
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<div class="app-section" data-guide-section="1">
|
||||
<h3>Megosztás másokkal</h3>
|
||||
|
||||
|
||||
@@ -278,7 +289,7 @@
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-faq="1">
|
||||
<div class="app-section" data-guide-faq="1">
|
||||
<h3>Gyakori kérdések</h3>
|
||||
<dl class="app-faq">
|
||||
<dt>Hol vannak a fájlok?</dt>
|
||||
|
||||
@@ -205,19 +205,30 @@
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
function appShotGone(img) {
|
||||
img.style.display = 'none';
|
||||
var box = img.parentNode, left = false;
|
||||
box.querySelectorAll('img').forEach(function (i) { if (i.style.display !== 'none') { left = true; } });
|
||||
if (!left) { box.closest('.app-section').style.display = 'none'; }
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app-section" data-screenshots-section="1">
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/privatebin-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/privatebin-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/privatebin-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
|
||||
<a href="/apps/privatebin/install" class="btn btn-primary" data-install-cta="1"
|
||||
onclick="return checkBeforeDeploy(event, 'privatebin')">Telepítés</a>
|
||||
@@ -235,7 +246,7 @@
|
||||
|
||||
|
||||
|
||||
<p class="form-hint" style="margin-top:1rem" data-guide-missing="1">Ehhez az alkalmazáshoz még nem készült útmutató.</p>
|
||||
<div class="app-section" data-guide-missing="1"><p>Ehhez az alkalmazáshoz még nem készült útmutató.</p></div>
|
||||
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
@@ -205,19 +205,30 @@
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
function appShotGone(img) {
|
||||
img.style.display = 'none';
|
||||
var box = img.parentNode, left = false;
|
||||
box.querySelectorAll('img').forEach(function (i) { if (i.style.display !== 'none') { left = true; } });
|
||||
if (!left) { box.closest('.app-section').style.display = 'none'; }
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app-section" data-screenshots-section="1">
|
||||
<div class="app-screenshots" id="screenshots">
|
||||
<img src="/static/assets/grocy-screenshot-1.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/grocy-screenshot-2.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
<img src="/static/assets/grocy-screenshot-3.webp" alt="" class="app-screenshot"
|
||||
onerror="this.style.display='none'">
|
||||
onerror="appShotGone(this)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<div class="app-section">
|
||||
|
||||
<a href="/apps/grocy/install" class="btn btn-primary" data-install-cta="1"
|
||||
onclick="return checkBeforeDeploy(event, 'grocy')">Telepítés</a>
|
||||
@@ -235,7 +246,7 @@
|
||||
|
||||
|
||||
|
||||
<p class="form-hint" style="margin-top:1rem" data-guide-missing="1">Ehhez az alkalmazáshoz még nem készült útmutató.</p>
|
||||
<div class="app-section" data-guide-missing="1"><p>Ehhez az alkalmazáshoz még nem készült útmutató.</p></div>
|
||||
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div data-tab-panel="logs">
|
||||
<div class="app-section" data-tab-panel="logs">
|
||||
<div class="logs-container" id="logs-container">
|
||||
<pre class="logs-output" id="logs-output">2026-09-14 line one
|
||||
line two</pre>
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -249,7 +249,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="setup-gate-card">
|
||||
<div class="app-section" id="setup-gate-card">
|
||||
<h3>Első beállítás</h3>
|
||||
<p>Most csak te éred el ezt az alkalmazást, amíg be vagy jelentkezve a vezérlőpultba. Így más nem hozhatja létre az első admin fiókot. Nyisd meg, és végezd el az első beállítást.</p>
|
||||
<p class="form-hint">Ha kész, a doboz magától észreveszi, és mindenkinek megnyitja az alkalmazást.</p>
|
||||
@@ -260,6 +260,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -273,17 +276,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -321,46 +319,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="close-signup-card">
|
||||
<div class="app-section" id="close-signup-card">
|
||||
<h3>Regisztráció</h3>
|
||||
<p>Ennél az alkalmazásnál bárki regisztrálhat, aki megtalálja a címét. Zárd le a regisztrációt most — a családtagjaidat utána is fel tudod venni.</p>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="appPress(this, '/apps/opengist/close-signup')">Regisztráció lezárása most</button>
|
||||
@@ -247,6 +247,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -260,17 +263,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -308,46 +306,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="close-signup-card">
|
||||
<div class="app-section" id="close-signup-card">
|
||||
<h3>Regisztráció</h3>
|
||||
<p>Ennél az alkalmazásnál bárki regisztrálhat, aki megtalálja a címét. Zárd le a regisztrációt most — a családtagjaidat utána is fel tudod venni.</p>
|
||||
<p class="form-hint" data-restarts-once="true">Ehhez az alkalmazás egyszer újraindul, és közben rövid ideig nem érhető el.</p>
|
||||
@@ -248,6 +248,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -261,17 +264,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -309,46 +307,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -252,6 +252,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
<h3>Kezdeti belépési adatok</h3>
|
||||
<table class="initcred-table">
|
||||
@@ -297,22 +300,17 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">Cím</div>
|
||||
<div class="app-field-value" data-app-address-row="1">paste.example.hu</div>
|
||||
</div>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -401,46 +399,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -242,6 +242,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -255,17 +258,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -303,46 +301,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="install-hold-card">
|
||||
<div class="app-section" id="install-hold-card">
|
||||
<h3>Még csak te éred el</h3>
|
||||
<p>A doboz most cseréli le az alkalmazás ismert első jelszavát egy saját, generált jelszóra. Addig csak te éred el, amíg be vagy jelentkezve a vezérlőpultba – más nem léphet be az ismert jelszóval. Ha a csere nem sikerül, változtasd meg a jelszót kézzel, és jelezd itt, hogy megtetted.</p>
|
||||
</div>
|
||||
@@ -245,6 +245,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -258,17 +261,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -306,46 +304,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+8
-47
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -241,6 +241,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
@@ -262,17 +265,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -310,46 +308,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+8
-47
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -241,6 +241,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -254,17 +257,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -302,46 +300,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+1212
File diff suppressed because it is too large
Load Diff
+1167
File diff suppressed because it is too large
Load Diff
@@ -228,7 +228,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -251,6 +251,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -264,17 +267,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -312,46 +310,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+8
-47
@@ -219,7 +219,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -242,6 +242,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -255,17 +258,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -303,46 +301,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+8
-47
@@ -224,7 +224,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -246,6 +246,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
<h3>Kezdeti belépési adatok</h3>
|
||||
<table class="initcred-table">
|
||||
@@ -279,17 +282,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -378,46 +376,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+38
-237
@@ -222,7 +222,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
<div class="flash flash-success">A mentés sikeresen elkészült.</div>
|
||||
<div class="flash flash-error">A művelet most nem sikerült.</div>
|
||||
|
||||
@@ -245,6 +245,20 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
<div class="app-section" id="app-login-panel" data-login-panel="1">
|
||||
<h3>Belépés az alkalmazásba</h3>
|
||||
<p>Az első bejelentkezéshez. Ha az alkalmazásban megváltoztatod a jelszót, itt a régi marad.</p>
|
||||
<div class="app-login-pair">
|
||||
<div class="app-login-field" data-login-field="ADMIN_PASSWORD">
|
||||
<label class="app-login-label" for="field-ADMIN_PASSWORD">Admin jelszó mező</label>
|
||||
<p class="form-hint" data-login-restored="ADMIN_PASSWORD">Mentésből töltötted vissza: a belépéshez a mentés idején érvényes jelszavad kell. Az itt tárolt érték nem az, ezért nem mutatjuk.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
@@ -256,8 +270,8 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-storage-info">
|
||||
<h4>Adattárolás</h4>
|
||||
<div class="app-section">
|
||||
<h3>Adattárolás</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Tárhely</span>
|
||||
@@ -284,8 +298,8 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="deploy-stale-data">
|
||||
<h4>Korábbi adatok</h4>
|
||||
<div class="app-section deploy-stale-data">
|
||||
<h3>Korábbi adatok</h3>
|
||||
<p class="form-hint" style="margin-bottom:1rem">
|
||||
Az alkalmazás adatainak másolata megtalálható egy másik tárolón is.
|
||||
Ez általában áthelyezés után marad hátra.
|
||||
@@ -314,7 +328,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Integrációk</h3>
|
||||
<p class="config-group-desc">
|
||||
Más telepített alkalmazásokkal való összekapcsolás. Az integráció automatikusan felfüggesztődik, ha bármelyik alkalmazás leáll, és újraaktiválódik indításkor.
|
||||
@@ -438,7 +452,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Földrajzi korlátozás</h3>
|
||||
<p class="config-group-desc">
|
||||
Az alkalmazás egyéni országkorlátozás nélkül a globális beállítást követi.
|
||||
@@ -573,7 +587,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Opcionális beállítások</h3>
|
||||
|
||||
<div class="config-group">
|
||||
@@ -672,7 +686,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Email-küldés</h3>
|
||||
<p class="config-group-desc">
|
||||
Ez az alkalmazás tud emailt küldeni (pl. jelszó-visszaállítás, meghívók) a Felhom-on keresztül,
|
||||
@@ -697,209 +711,33 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">Cím</div>
|
||||
<div class="app-field-value" data-app-address-row="1">paste.example.hu</div>
|
||||
</div>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
<div class="form-section">
|
||||
<h4>Automatikusan generált értékek</h4>
|
||||
|
||||
<p class="form-section-desc">Ezek az értékek automatikusan jöttek létre a telepítéskor.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Titkos kulcs érték <span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> Automatikusan generálva</span></label>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('privatebin','SECRET_KEY', this)">Megjelenítés</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Adatbázis neve itt </label>
|
||||
|
||||
|
||||
<input type="text" id="auto-field-DB_NAME" class="form-control" value="privatebin_db" readonly>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Üres mező értéke </label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">Tárhely</div>
|
||||
<div class="app-field-value" data-app-storage-row="1">HDD egy</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-section">
|
||||
<h4>Beállítások</h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-LANG_SEL">
|
||||
Felület nyelve
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control"
|
||||
disabled>
|
||||
|
||||
<option value="hu">Magyar nyelv</option>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
<div class="app-field-row" data-setting-row="SECRET_KEY">
|
||||
<div class="app-field-label">Titkos kulcs érték</div>
|
||||
<div class="app-field-value input-with-button">
|
||||
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="" placeholder="••••••••••••" readonly aria-label="Titkos kulcs érték">
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('privatebin','SECRET_KEY', this)">Megjelenítés</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-ADMIN_PASSWORD">
|
||||
Admin jelszó mező
|
||||
<span class="required">*</span>
|
||||
<span class="locked-hint">telepítés után nem módosítható</span>
|
||||
</label>
|
||||
|
||||
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-ADMIN_PASSWORD" class="form-control" value="" placeholder="••••••••••••" disabled>
|
||||
</div>
|
||||
|
||||
<span class="form-hint">Mentésből töltötted vissza: a belépéshez a mentés idején érvényes jelszavad kell. Az itt tárolt érték nem az, ezért nem mutatjuk.</span>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-FLAG">
|
||||
Kapcsoló mező itt
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="field-FLAG" name="FLAG" value="true"
|
||||
disabled>
|
||||
<span class="toggle-label">Igen</span>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
<span class="form-hint">Egy kapcsoló leírása</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-HDD_PATH">
|
||||
Adatok helye itt
|
||||
<span class="required">*</span>
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
<select id="field-HDD_PATH" name="HDD_PATH" class="form-control"
|
||||
disabled
|
||||
onchange="checkStorageSpace(this)">
|
||||
|
||||
<option value="/mnt/hdd1" data-free-percent="50"
|
||||
|
||||
|
||||
selected>
|
||||
HDD egy — 500 GB szabad (alapértelmezett)
|
||||
</option>
|
||||
|
||||
<option value="/mnt/nas" data-free-percent="90"
|
||||
disabled
|
||||
|
||||
>
|
||||
NAS egy — 900 GB szabad (hálózati tárhely nem használható)
|
||||
</option>
|
||||
|
||||
<option value="/mnt/hdd2" data-free-percent="10"
|
||||
|
||||
|
||||
>
|
||||
HDD kettő — 100 GB szabad
|
||||
</option>
|
||||
|
||||
|
||||
</select>
|
||||
<div id="storage-space-warn" class="form-hint" style="color:var(--warn);display:none">
|
||||
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> A kiválasztott tárhely majdnem megtelt.
|
||||
</div>
|
||||
<div class="form-hint" style="margin-top:.4rem;opacity:.8">
|
||||
<svg class="ico ico-sm"><use href="#i-info"/></svg> A kiválasztott meghajtón az alkalmazás <strong>fájljai</strong> (média, dokumentumok) tárolódnak.
|
||||
Az <strong>adatbázis a gyors belső SSD-n</strong> fut — és az alkalmazással együtt készül róla biztonsági mentés.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-NOTE">
|
||||
Megjegyzés mező
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<input type="text" id="field-NOTE" name="NOTE"
|
||||
class="form-control" value=""
|
||||
placeholder="Írj ide valamit"
|
||||
|
||||
disabled>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
<div class="app-field-row" data-setting-row="LANG_SEL">
|
||||
<div class="app-field-label">Felület nyelve</div>
|
||||
<div class="app-field-value">Magyar nyelv</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -937,46 +775,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
<div class="flash flash-success">A mentés sikeresen elkészült.</div>
|
||||
<div class="flash flash-error">A művelet most nem sikerült.</div>
|
||||
|
||||
@@ -245,6 +245,30 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
<div class="app-section" id="app-login-panel" data-login-panel="1">
|
||||
<h3>Belépés az alkalmazásba</h3>
|
||||
<p>Az első bejelentkezéshez. Ha az alkalmazásban megváltoztatod a jelszót, itt a régi marad.</p>
|
||||
<div class="app-login-pair">
|
||||
<div class="app-login-field" data-login-field="ADMIN_USER">
|
||||
<label class="app-login-label" for="field-ADMIN_USER">Admin név mező</label>
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-ADMIN_USER" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-login-reveal="ADMIN_USER" onclick="revealAutoField('privatebin','ADMIN_USER', this, 'field-ADMIN_USER')">Megjelenítés</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-login-field" data-login-field="ADMIN_PASSWORD">
|
||||
<label class="app-login-label" for="field-ADMIN_PASSWORD">Admin jelszó mező</label>
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-ADMIN_PASSWORD" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-login-reveal="ADMIN_PASSWORD" onclick="revealAutoField('privatebin','ADMIN_PASSWORD', this, 'field-ADMIN_PASSWORD')">Megjelenítés</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
@@ -256,8 +280,8 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-storage-info">
|
||||
<h4>Adattárolás</h4>
|
||||
<div class="app-section">
|
||||
<h3>Adattárolás</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Tárhely</span>
|
||||
@@ -284,8 +308,8 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="deploy-stale-data">
|
||||
<h4>Korábbi adatok</h4>
|
||||
<div class="app-section deploy-stale-data">
|
||||
<h3>Korábbi adatok</h3>
|
||||
<p class="form-hint" style="margin-bottom:1rem">
|
||||
Az alkalmazás adatainak másolata megtalálható egy másik tárolón is.
|
||||
Ez általában áthelyezés után marad hátra.
|
||||
@@ -314,7 +338,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Integrációk</h3>
|
||||
<p class="config-group-desc">
|
||||
Más telepített alkalmazásokkal való összekapcsolás. Az integráció automatikusan felfüggesztődik, ha bármelyik alkalmazás leáll, és újraaktiválódik indításkor.
|
||||
@@ -438,7 +462,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Földrajzi korlátozás</h3>
|
||||
<p class="config-group-desc">
|
||||
Az alkalmazás egyéni országkorlátozás nélkül a globális beállítást követi.
|
||||
@@ -573,7 +597,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Opcionális beállítások</h3>
|
||||
|
||||
<div class="config-group">
|
||||
@@ -672,7 +696,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Email-küldés</h3>
|
||||
<p class="config-group-desc">
|
||||
Ez az alkalmazás tud emailt küldeni (pl. jelszó-visszaállítás, meghívók) a Felhom-on keresztül,
|
||||
@@ -697,212 +721,33 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">Cím</div>
|
||||
<div class="app-field-value" data-app-address-row="1">paste.example.hu</div>
|
||||
</div>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
<div class="form-section">
|
||||
<h4>Automatikusan generált értékek</h4>
|
||||
|
||||
<p class="form-section-desc">Ezek az értékek automatikusan jöttek létre a telepítéskor.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Titkos kulcs érték <span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> Automatikusan generálva</span></label>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('privatebin','SECRET_KEY', this)">Megjelenítés</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Adatbázis neve itt </label>
|
||||
|
||||
|
||||
<input type="text" id="auto-field-DB_NAME" class="form-control" value="privatebin_db" readonly>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Üres mező értéke </label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">Tárhely</div>
|
||||
<div class="app-field-value" data-app-storage-row="1">HDD egy</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-section">
|
||||
<h4>Beállítások</h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-LANG_SEL">
|
||||
Felület nyelve
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control"
|
||||
disabled>
|
||||
|
||||
<option value="hu">Magyar nyelv</option>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
<div class="app-field-row" data-setting-row="SECRET_KEY">
|
||||
<div class="app-field-label">Titkos kulcs érték</div>
|
||||
<div class="app-field-value input-with-button">
|
||||
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="" placeholder="••••••••••••" readonly aria-label="Titkos kulcs érték">
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('privatebin','SECRET_KEY', this)">Megjelenítés</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-ADMIN_PASSWORD">
|
||||
Admin jelszó mező
|
||||
<span class="required">*</span>
|
||||
<span class="locked-hint">telepítés után nem módosítható</span>
|
||||
</label>
|
||||
|
||||
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-ADMIN_PASSWORD" class="form-control" value="" placeholder="••••••••••••" disabled>
|
||||
<button type="button" class="btn btn-sm btn-outline pw-toggle-btn"
|
||||
onclick="revealPasswordField('privatebin', 'ADMIN_PASSWORD', this)"
|
||||
title="Megjelenítés">👁</button>
|
||||
</div>
|
||||
|
||||
<span class="form-hint">Telepítéskor beállított kezdeti jelszó — ha az alkalmazásban megváltoztattad, az itt nem frissül.</span>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-FLAG">
|
||||
Kapcsoló mező itt
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="field-FLAG" name="FLAG" value="true"
|
||||
disabled>
|
||||
<span class="toggle-label">Igen</span>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
<span class="form-hint">Egy kapcsoló leírása</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-HDD_PATH">
|
||||
Adatok helye itt
|
||||
<span class="required">*</span>
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
<select id="field-HDD_PATH" name="HDD_PATH" class="form-control"
|
||||
disabled
|
||||
onchange="checkStorageSpace(this)">
|
||||
|
||||
<option value="/mnt/hdd1" data-free-percent="50"
|
||||
|
||||
|
||||
selected>
|
||||
HDD egy — 500 GB szabad (alapértelmezett)
|
||||
</option>
|
||||
|
||||
<option value="/mnt/nas" data-free-percent="90"
|
||||
disabled
|
||||
|
||||
>
|
||||
NAS egy — 900 GB szabad (hálózati tárhely nem használható)
|
||||
</option>
|
||||
|
||||
<option value="/mnt/hdd2" data-free-percent="10"
|
||||
|
||||
|
||||
>
|
||||
HDD kettő — 100 GB szabad
|
||||
</option>
|
||||
|
||||
|
||||
</select>
|
||||
<div id="storage-space-warn" class="form-hint" style="color:var(--warn);display:none">
|
||||
<svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> A kiválasztott tárhely majdnem megtelt.
|
||||
</div>
|
||||
<div class="form-hint" style="margin-top:.4rem;opacity:.8">
|
||||
<svg class="ico ico-sm"><use href="#i-info"/></svg> A kiválasztott meghajtón az alkalmazás <strong>fájljai</strong> (média, dokumentumok) tárolódnak.
|
||||
Az <strong>adatbázis a gyors belső SSD-n</strong> fut — és az alkalmazással együtt készül róla biztonsági mentés.
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-NOTE">
|
||||
Megjegyzés mező
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<input type="text" id="field-NOTE" name="NOTE"
|
||||
class="form-control" value=""
|
||||
placeholder="Írj ide valamit"
|
||||
|
||||
disabled>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
<div class="app-field-row" data-setting-row="LANG_SEL">
|
||||
<div class="app-field-label">Felület nyelve</div>
|
||||
<div class="app-field-value">Magyar nyelv</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -940,46 +785,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="setup-gate-card">
|
||||
<div class="app-section" id="setup-gate-card">
|
||||
<h3>Első beállítás</h3>
|
||||
<p>Most csak te éred el ezt az alkalmazást, amíg be vagy jelentkezve a vezérlőpultba. Így más nem hozhatja létre az első admin fiókot. Nyisd meg, és végezd el az első beállítást.</p>
|
||||
<p class="form-hint">Ha kész, nyomd meg ezt a gombot. Addig a telefonos alkalmazások és a család többi tagja nem éri el.</p>
|
||||
@@ -248,6 +248,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -261,17 +264,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -309,46 +307,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="setup-gate-card">
|
||||
<div class="app-section" id="setup-gate-card">
|
||||
<h3>Első beállítás</h3>
|
||||
<p>Most csak te éred el ezt az alkalmazást, amíg be vagy jelentkezve a vezérlőpultba. Így más nem hozhatja létre az első admin fiókot. Nyisd meg, és végezd el az első beállítást.</p>
|
||||
<p class="form-hint" data-restarts-once="true">Ehhez az alkalmazás egyszer újraindul, és közben rövid ideig nem érhető el.</p>
|
||||
@@ -249,6 +249,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -262,17 +265,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -310,46 +308,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="setup-gate-card">
|
||||
<div class="app-section" id="setup-gate-card">
|
||||
<h3>Első beállítás</h3>
|
||||
<p>Most csak te éred el ezt az alkalmazást, amíg be vagy jelentkezve a vezérlőpultba. Így más nem hozhatja létre az első admin fiókot. Nyisd meg, és végezd el az első beállítást.</p>
|
||||
<p class="form-hint">Ha kész, a doboz magától észreveszi, és mindenkinek megnyitja az alkalmazást.</p>
|
||||
@@ -248,6 +248,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -261,17 +264,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -309,46 +307,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="signup-card">
|
||||
<div class="app-section" id="signup-card">
|
||||
<h3>Regisztráció</h3>
|
||||
<p>Az első admin fiók után a regisztráció zárva: idegen nem hozhat létre fiókot.</p>
|
||||
<p class="form-hint" id="add-people">Családtagot így adhatsz hozzá: Admin panel → Users.</p>
|
||||
@@ -248,6 +248,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -261,17 +264,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -309,46 +307,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="signup-card">
|
||||
<div class="app-section" id="signup-card">
|
||||
<h3>Regisztráció</h3>
|
||||
<p>Az első admin fiók után a regisztráció zárva: idegen nem hozhat létre fiókot.</p>
|
||||
<p class="form-hint" id="add-people">Családtagot így adhatsz hozzá: Admin panel → Users.</p>
|
||||
@@ -250,6 +250,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -263,17 +266,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -311,46 +309,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="signup-card">
|
||||
<div class="app-section" id="signup-card">
|
||||
<h3>Regisztráció</h3>
|
||||
<p>Az első admin fiók után a regisztráció zárva: idegen nem hozhat létre fiókot.</p>
|
||||
<p class="form-hint" id="add-people">Családtagot így adhatsz hozzá: Admin panel → Users.</p>
|
||||
@@ -249,6 +249,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -262,17 +265,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -310,46 +308,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
+9
-48
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<div class="app-section" style="margin-top:1rem" id="signup-card">
|
||||
<div class="app-section" id="signup-card">
|
||||
<h3>Regisztráció</h3>
|
||||
<p>A regisztráció most nyitva, eddig: 14:05</p>
|
||||
<p class="form-hint" id="add-people">Családtagot így adhatsz hozzá: Admin panel → Users.</p>
|
||||
@@ -246,6 +246,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -259,17 +262,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -307,46 +305,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
@@ -218,7 +218,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
<div class="flash flash-success">A mentés sikeresen elkészült.</div>
|
||||
<div class="flash flash-error">A művelet most nem sikerült.</div>
|
||||
|
||||
@@ -240,6 +240,30 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
<div class="app-section" id="app-login-panel" data-login-panel="1">
|
||||
<h3>Belépés az alkalmazásba</h3>
|
||||
<p>Az első bejelentkezéshez. Ha az alkalmazásban megváltoztatod a jelszót, itt a régi marad.</p>
|
||||
<div class="app-login-pair">
|
||||
<div class="app-login-field" data-login-field="ADMIN_USER">
|
||||
<label class="app-login-label" for="field-ADMIN_USER">Admin név mező</label>
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-ADMIN_USER" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-login-reveal="ADMIN_USER" onclick="revealAutoField('privatebin','ADMIN_USER', this, 'field-ADMIN_USER')">Megjelenítés</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-login-field" data-login-field="ADMIN_PASSWORD">
|
||||
<label class="app-login-label" for="field-ADMIN_PASSWORD">Admin jelszó mező</label>
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-ADMIN_PASSWORD" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" data-login-reveal="ADMIN_PASSWORD" onclick="revealAutoField('privatebin','ADMIN_PASSWORD', this, 'field-ADMIN_PASSWORD')">Megjelenítés</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
@@ -251,8 +275,8 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-storage-info">
|
||||
<h4>Adattárolás</h4>
|
||||
<div class="app-section">
|
||||
<h3>Adattárolás</h3>
|
||||
<div class="settings-grid">
|
||||
<div class="settings-row">
|
||||
<span class="settings-label">Tárhely</span>
|
||||
@@ -266,7 +290,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Integrációk</h3>
|
||||
<p class="config-group-desc">
|
||||
Más telepített alkalmazásokkal való összekapcsolás. Az integráció automatikusan felfüggesztődik, ha bármelyik alkalmazás leáll, és újraaktiválódik indításkor.
|
||||
@@ -390,7 +414,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Földrajzi korlátozás</h3>
|
||||
<p class="config-group-desc">
|
||||
Az alkalmazás egyéni országkorlátozás nélkül a globális beállítást követi.
|
||||
@@ -525,7 +549,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Opcionális beállítások</h3>
|
||||
|
||||
<div class="config-group">
|
||||
@@ -624,7 +648,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-optional-config">
|
||||
<div class="app-section">
|
||||
<h3>Email-küldés</h3>
|
||||
<p class="config-group-desc">
|
||||
Ez az alkalmazás tud emailt küldeni (pl. jelszó-visszaállítás, meghívók) a Felhom-on keresztül,
|
||||
@@ -644,184 +668,33 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">Cím</div>
|
||||
<div class="app-field-value" data-app-address-row="1">paste.example.hu</div>
|
||||
</div>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
<div class="form-section">
|
||||
<h4>Automatikusan generált értékek</h4>
|
||||
|
||||
<p class="form-section-desc">Ezek az értékek automatikusan jöttek létre a telepítéskor.</p>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Titkos kulcs érték <span class="auto-generated-badge"><svg class="ico ico-sm"><use href="#i-check"/></svg> Automatikusan generálva</span></label>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="" placeholder="••••••••••••" readonly>
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('privatebin','SECRET_KEY', this)">Megjelenítés</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Adatbázis neve itt </label>
|
||||
|
||||
|
||||
<input type="text" id="auto-field-DB_NAME" class="form-control" value="privatebin_db" readonly>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group form-group-auto">
|
||||
<label>Üres mező értéke </label>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">Tárhely</div>
|
||||
<div class="app-field-value" data-app-storage-row="1">/mnt/gone (nem elérhető)</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-section">
|
||||
<h4>Beállítások</h4>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-LANG_SEL">
|
||||
Felület nyelve
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<select id="field-LANG_SEL" name="LANG_SEL" class="form-control"
|
||||
disabled>
|
||||
|
||||
<option value="hu">Magyar nyelv</option>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
|
||||
<div class="app-field-row" data-setting-row="SECRET_KEY">
|
||||
<div class="app-field-label">Titkos kulcs érték</div>
|
||||
<div class="app-field-value input-with-button">
|
||||
<input type="password" id="auto-field-SECRET_KEY" class="form-control" value="" placeholder="••••••••••••" readonly aria-label="Titkos kulcs érték">
|
||||
<button type="button" class="btn btn-sm btn-outline" onclick="revealAutoField('privatebin','SECRET_KEY', this)">Megjelenítés</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-ADMIN_PASSWORD">
|
||||
Admin jelszó mező
|
||||
<span class="required">*</span>
|
||||
<span class="locked-hint">telepítés után nem módosítható</span>
|
||||
</label>
|
||||
|
||||
|
||||
<div class="input-with-button">
|
||||
<input type="password" id="field-ADMIN_PASSWORD" class="form-control" value="" placeholder="••••••••••••" disabled>
|
||||
<button type="button" class="btn btn-sm btn-outline pw-toggle-btn"
|
||||
onclick="revealPasswordField('privatebin', 'ADMIN_PASSWORD', this)"
|
||||
title="Megjelenítés">👁</button>
|
||||
</div>
|
||||
|
||||
<span class="form-hint">Telepítéskor beállított kezdeti jelszó — ha az alkalmazásban megváltoztattad, az itt nem frissül.</span>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-FLAG">
|
||||
Kapcsoló mező itt
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<label class="toggle">
|
||||
<input type="checkbox" id="field-FLAG" name="FLAG" value="true"
|
||||
disabled>
|
||||
<span class="toggle-label">Igen</span>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
<span class="form-hint">Egy kapcsoló leírása</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-HDD_PATH">
|
||||
Adatok helye itt
|
||||
<span class="required">*</span>
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
<input type="text" id="field-HDD_PATH" name="HDD_PATH"
|
||||
class="form-control" value=""
|
||||
placeholder="/mnt/valahol"
|
||||
required
|
||||
disabled>
|
||||
<span class="form-hint" style="color:var(--warn)">Nincs regisztrált adattároló — add meg kézzel az útvonalat</span>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label for="field-NOTE">
|
||||
Megjegyzés mező
|
||||
|
||||
|
||||
</label>
|
||||
|
||||
|
||||
<input type="text" id="field-NOTE" name="NOTE"
|
||||
class="form-control" value=""
|
||||
placeholder="Írj ide valamit"
|
||||
|
||||
disabled>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
<div class="app-field-row" data-setting-row="LANG_SEL">
|
||||
<div class="app-field-label">Felület nyelve</div>
|
||||
<div class="app-field-value">Magyar nyelv</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -859,46 +732,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
@@ -220,7 +220,7 @@
|
||||
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-settings-tab">
|
||||
|
||||
|
||||
|
||||
@@ -242,6 +242,9 @@
|
||||
|
||||
<div class="app-sections">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -255,17 +258,12 @@
|
||||
|
||||
|
||||
|
||||
<div class="app-section">
|
||||
|
||||
<div class="app-section" data-address-keys="1">
|
||||
<h3>Cím és kulcsok</h3>
|
||||
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">Ezek telepítéskor jöttek létre, és nem módosíthatók.</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -303,46 +301,9 @@ function appAfterStart(btn, slug, envVar) {
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function checkStorageSpace(sel) {
|
||||
var opt = sel.options[sel.selectedIndex];
|
||||
var warn = document.getElementById('storage-space-warn');
|
||||
if (!warn) return;
|
||||
var freePct = parseFloat(opt.getAttribute('data-free-percent') || '100');
|
||||
warn.style.display = freePct < 20 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var sel = document.querySelector('select[onchange="checkStorageSpace(this)"]');
|
||||
if (sel) checkStorageSpace(sel);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
function revealPasswordField(stackName, envVar, btn) {
|
||||
var el = document.getElementById('field-' + envVar);
|
||||
if (!el) return;
|
||||
if (btn.dataset.shown === '1') {
|
||||
el.value = '';
|
||||
el.type = 'password';
|
||||
btn.dataset.shown = '';
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
fetch('/stacks/' + encodeURIComponent(stackName) + '/auto-field/reveal', {
|
||||
method: 'POST',
|
||||
headers: Object.assign({'Content-Type': 'application/x-www-form-urlencoded'}, csrfHeaders()),
|
||||
credentials: 'same-origin',
|
||||
body: 'env_var=' + encodeURIComponent(envVar)
|
||||
}).then(function (r) { return r.json(); }).then(function (j) {
|
||||
btn.disabled = false;
|
||||
if (!j.ok) { showAlert(j.error || 'A lekérés nem sikerült.'); return; }
|
||||
el.value = j.data.value;
|
||||
el.type = 'text';
|
||||
el.dataset.revealed = '1';
|
||||
btn.dataset.shown = '1';
|
||||
}).catch(function () { btn.disabled = false; showAlert('A lekérés nem sikerült.'); });
|
||||
}
|
||||
function revealAutoField(stackName, envVar, btn, elId) {
|
||||
var el = document.getElementById(elId || ('auto-field-' + envVar));
|
||||
if (!el) return;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1197
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user