v0.27.2 — copyable error popups, Tier2 hub reporting, memory bar fixes, new labels
- Replace native alert() with custom showAlert() modal (text selectable) - Manual Tier2 backup now pushes infra backup to Hub - CommittedMemory() excludes stopped/exited apps - Pre-start memory check blocks start if insufficient RAM - Add hungarian_ui metadata field + "Magyar felület" badge - Add "USB" badge on storage cards in settings page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,21 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### v0.27.2 — Comprehensive Fixes and New Labels (2026-02-23)
|
||||||
|
|
||||||
|
#### Fixed
|
||||||
|
- **Deploy error popups now copyable** — Replaced all native `alert()` calls with a custom modal (`showAlert()` in layout.html) using a `<pre>` block with `user-select:text`. Error messages can now be selected and copied. Applied across deploy.html and layout.html.
|
||||||
|
- **Manual Tier2 backup now reports to Hub** — Added `OnCrossDriveComplete` callback to `Router` (`internal/api/router.go`). Both `triggerCrossBackup` (single-app) and `triggerAllCrossBackups` (run-all) now call `pushInfraBackup()` + `writeLocalInfraBackup()` after completion, matching the automatic scheduled path.
|
||||||
|
- **Memory bar excludes stopped apps** — `CommittedMemory()` in `internal/stacks/manager.go` now skips apps with `StateStopped` or `StateExited`. Only running/starting/unhealthy apps count toward committed memory.
|
||||||
|
- **Pre-start memory check** — `actionStack("start")` in `internal/api/router.go` now validates available memory before starting a stopped app. Returns 409 Conflict with a descriptive Hungarian error if insufficient.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- **`hungarian_ui` metadata field** — New `HungarianUI bool` field in `ResourceHints` (`internal/stacks/metadata.go`). Shows "Magyar felület" green badge on deploy, stacks, and app info pages when `hungarian_ui: true` in `.felhom.yml`.
|
||||||
|
- **USB badge on storage cards** — Settings page storage cards now show an orange "USB" badge next to Aktív/Alapértelmezett when the drive is USB-attached (using existing `IsUSB` sysfs detection).
|
||||||
|
- **`StackMemoryMB()` helper** — New method on `Manager` to get a specific stack's memory request.
|
||||||
|
|
||||||
|
#### App Catalog (app-catalog-felhom.eu)
|
||||||
|
- **AdventureLog** — Fixed image tags from `v0.12.0` (non-existent) to `v0.11.0` for both backend and frontend.
|
||||||
|
|
||||||
### v0.27.1 — Fix FileBrowser Mount Sync (2026-02-22)
|
### v0.27.1 — Fix FileBrowser Mount Sync (2026-02-22)
|
||||||
|
|
||||||
#### Fixed
|
#### Fixed
|
||||||
|
|||||||
@@ -580,6 +580,10 @@ func main() {
|
|||||||
apiRouter.OnConfigApplied = func() {
|
apiRouter.OnConfigApplied = func() {
|
||||||
pushInfraBackup(cfg, sett, stackProv, hubPusher, logger)
|
pushInfraBackup(cfg, sett, stackProv, hubPusher, logger)
|
||||||
}
|
}
|
||||||
|
apiRouter.OnCrossDriveComplete = func() {
|
||||||
|
pushInfraBackup(cfg, sett, stackProv, hubPusher, logger)
|
||||||
|
writeLocalInfraBackup(cfg, sett, stackProv, logger)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if assetsSyncer != nil {
|
if assetsSyncer != nil {
|
||||||
apiRouter.SetAssetsSyncer(assetsSyncer)
|
apiRouter.SetAssetsSyncer(assetsSyncer)
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ type Router struct {
|
|||||||
// OnConfigApplied is called after a successful config apply (e.g., to push infra backup).
|
// OnConfigApplied is called after a successful config apply (e.g., to push infra backup).
|
||||||
OnConfigApplied func()
|
OnConfigApplied func()
|
||||||
|
|
||||||
|
// OnCrossDriveComplete is called after a manual cross-drive backup completes (to push infra backup to Hub).
|
||||||
|
OnCrossDriveComplete func()
|
||||||
|
|
||||||
// Asset syncer for on-demand Hub asset sync
|
// Asset syncer for on-demand Hub asset sync
|
||||||
assetsSyncer *assets.Syncer
|
assetsSyncer *assets.Syncer
|
||||||
|
|
||||||
@@ -327,6 +330,26 @@ func (r *Router) actionStack(w http.ResponseWriter, action, name string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Memory check before starting a stopped app
|
||||||
|
if action == "start" {
|
||||||
|
stackMemMB := r.stackMgr.StackMemoryMB(name)
|
||||||
|
if stackMemMB > 0 {
|
||||||
|
if totalMB, memErr := system.GetTotalMemoryMB(); memErr == nil {
|
||||||
|
reservedMB := r.cfg.System.ReservedMemoryMB
|
||||||
|
usableMB := totalMB - reservedMB
|
||||||
|
committedReqMB, _ := r.stackMgr.CommittedMemory()
|
||||||
|
afterMB := committedReqMB + stackMemMB
|
||||||
|
if afterMB > usableMB {
|
||||||
|
writeJSON(w, http.StatusConflict, apiResponse{
|
||||||
|
OK: false,
|
||||||
|
Error: fmt.Sprintf("Nincs elég memória az indításhoz. Szükséges: %d MB, elérhető: %d MB (foglalt: %d MB / használható: %d MB)", stackMemMB, usableMB-committedReqMB, committedReqMB, usableMB),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch action {
|
switch action {
|
||||||
case "start":
|
case "start":
|
||||||
@@ -807,6 +830,9 @@ func (r *Router) triggerCrossBackup(w http.ResponseWriter, req *http.Request, na
|
|||||||
if err := r.crossDriveRunner.RunAppBackup(context.Background(), name); err != nil {
|
if err := r.crossDriveRunner.RunAppBackup(context.Background(), name); err != nil {
|
||||||
r.logger.Printf("[API] Cross-drive backup failed for %s: %v", name, err)
|
r.logger.Printf("[API] Cross-drive backup failed for %s: %v", name, err)
|
||||||
}
|
}
|
||||||
|
if r.OnCrossDriveComplete != nil {
|
||||||
|
r.OnCrossDriveComplete()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Mentés elindítva"})
|
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Mentés elindítva"})
|
||||||
@@ -849,6 +875,9 @@ func (r *Router) triggerAllCrossBackups(w http.ResponseWriter, _ *http.Request)
|
|||||||
if err := r.crossDriveRunner.RunAllScheduled(ctx, "manual"); err != nil {
|
if err := r.crossDriveRunner.RunAllScheduled(ctx, "manual"); err != nil {
|
||||||
r.logger.Printf("[API] Cross-drive run-all manual error: %v", err)
|
r.logger.Printf("[API] Cross-drive run-all manual error: %v", err)
|
||||||
}
|
}
|
||||||
|
if r.OnCrossDriveComplete != nil {
|
||||||
|
r.OnCrossDriveComplete()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Összes mentés elindítva"})
|
writeJSON(w, http.StatusOK, apiResponse{OK: true, Message: "Összes mentés elindítva"})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -756,7 +756,9 @@ func ParseMemoryMB(s string) int {
|
|||||||
return int(val)
|
return int(val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommittedMemory returns the sum of mem_request and mem_limit across all deployed stacks.
|
// CommittedMemory returns the sum of mem_request and mem_limit across all
|
||||||
|
// deployed stacks that are currently running (or starting/unhealthy/restarting).
|
||||||
|
// Stopped and exited apps are excluded since they do not consume memory.
|
||||||
func (m *Manager) CommittedMemory() (requestMB int, limitMB int) {
|
func (m *Manager) CommittedMemory() (requestMB int, limitMB int) {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
@@ -765,12 +767,25 @@ func (m *Manager) CommittedMemory() (requestMB int, limitMB int) {
|
|||||||
if !s.Deployed {
|
if !s.Deployed {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if s.State == StateStopped || s.State == StateExited {
|
||||||
|
continue
|
||||||
|
}
|
||||||
requestMB += ParseMemoryMB(s.Meta.Resources.MemRequest)
|
requestMB += ParseMemoryMB(s.Meta.Resources.MemRequest)
|
||||||
limitMB += ParseMemoryMB(s.Meta.Resources.MemLimit)
|
limitMB += ParseMemoryMB(s.Meta.Resources.MemLimit)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StackMemoryMB returns the mem_request for a specific stack.
|
||||||
|
func (m *Manager) StackMemoryMB(name string) int {
|
||||||
|
m.mu.RLock()
|
||||||
|
defer m.mu.RUnlock()
|
||||||
|
if s, ok := m.stacks[name]; ok {
|
||||||
|
return ParseMemoryMB(s.Meta.Resources.MemRequest)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
// getCatalogTemplateSlugs reads the synced catalog cache and returns a set of
|
// getCatalogTemplateSlugs reads the synced catalog cache and returns a set of
|
||||||
// template slugs (directory names) that have a docker-compose.yml.
|
// template slugs (directory names) that have a docker-compose.yml.
|
||||||
func (m *Manager) getCatalogTemplateSlugs() map[string]bool {
|
func (m *Manager) getCatalogTemplateSlugs() map[string]bool {
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ type ResourceHints struct {
|
|||||||
MemLimit string `yaml:"mem_limit" json:"mem_limit"`
|
MemLimit string `yaml:"mem_limit" json:"mem_limit"`
|
||||||
PiCompatible bool `yaml:"pi_compatible" json:"pi_compatible"`
|
PiCompatible bool `yaml:"pi_compatible" json:"pi_compatible"`
|
||||||
NeedsHDD bool `yaml:"needs_hdd" json:"needs_hdd"`
|
NeedsHDD bool `yaml:"needs_hdd" json:"needs_hdd"`
|
||||||
|
HungarianUI bool `yaml:"hungarian_ui" json:"hungarian_ui"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployField defines one configuration field shown during first deployment.
|
// DeployField defines one configuration field shown during first deployment.
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
<span class="meta-badge">{{.Meta.Category}}</span>
|
<span class="meta-badge">{{.Meta.Category}}</span>
|
||||||
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge meta-badge-warn">HDD szükséges</span>{{end}}
|
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge meta-badge-warn">HDD szükséges</span>{{end}}
|
||||||
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{else}}<span class="meta-badge meta-badge-warn">Csak x86</span>{{end}}
|
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{else}}<span class="meta-badge meta-badge-warn">Csak x86</span>{{end}}
|
||||||
|
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">Magyar felület</span>{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
{{if .Meta.Resources.MemRequest}}<span class="meta-badge">~{{.Meta.Resources.MemRequest}}</span>{{end}}
|
{{if .Meta.Resources.MemRequest}}<span class="meta-badge">~{{.Meta.Resources.MemRequest}}</span>{{end}}
|
||||||
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{end}}
|
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{end}}
|
||||||
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge">HDD szükséges</span>{{end}}
|
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge">HDD szükséges</span>{{end}}
|
||||||
|
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">Magyar felület</span>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline" style="margin-top:0.5rem">
|
<a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline" style="margin-top:0.5rem">
|
||||||
Részletes leírás, képernyőképek
|
Részletes leírás, képernyőképek
|
||||||
@@ -402,7 +403,7 @@ function triggerCrossDriveBackup(stackName, btn) {
|
|||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(d) {
|
.then(function(d) {
|
||||||
if (!d.ok) {
|
if (!d.ok) {
|
||||||
alert('Hiba: ' + (d.error || 'Ismeretlen hiba'));
|
showAlert('Hiba: ' + (d.error || 'Ismeretlen hiba'));
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Mentés most';
|
btn.textContent = 'Mentés most';
|
||||||
return;
|
return;
|
||||||
@@ -421,7 +422,7 @@ function triggerCrossDriveBackup(stackName, btn) {
|
|||||||
btn.textContent = 'Mentés kész';
|
btn.textContent = 'Mentés kész';
|
||||||
} else {
|
} else {
|
||||||
btn.textContent = 'Hiba';
|
btn.textContent = 'Hiba';
|
||||||
alert('Hiba: ' + (s.data.last_error || 'Ismeretlen hiba'));
|
showAlert('Hiba: ' + (s.data.last_error || 'Ismeretlen hiba'));
|
||||||
}
|
}
|
||||||
setTimeout(function() { location.reload(); }, 2000);
|
setTimeout(function() { location.reload(); }, 2000);
|
||||||
}
|
}
|
||||||
@@ -429,7 +430,7 @@ function triggerCrossDriveBackup(stackName, btn) {
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
})
|
})
|
||||||
.catch(function(e) {
|
.catch(function(e) {
|
||||||
alert('Hálózati hiba: ' + e.message);
|
showAlert('Hálózati hiba: ' + e.message);
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Mentés most';
|
btn.textContent = 'Mentés most';
|
||||||
});
|
});
|
||||||
@@ -485,7 +486,7 @@ function deleteStaleData(stackName, stalePath, btn) {
|
|||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (!data.ok) {
|
if (!data.ok) {
|
||||||
alert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Korábbi adatok törlése';
|
btn.textContent = 'Korábbi adatok törlése';
|
||||||
return;
|
return;
|
||||||
@@ -494,7 +495,7 @@ function deleteStaleData(stackName, stalePath, btn) {
|
|||||||
if (data.errors && data.errors.length > 0) {
|
if (data.errors && data.errors.length > 0) {
|
||||||
msg += '\n\nNéhány hiba történt:\n' + data.errors.join('\n');
|
msg += '\n\nNéhány hiba történt:\n' + data.errors.join('\n');
|
||||||
}
|
}
|
||||||
alert(msg);
|
showAlert(msg);
|
||||||
// Remove the stale data card from DOM
|
// Remove the stale data card from DOM
|
||||||
var item = btn.closest('.stale-data-item');
|
var item = btn.closest('.stale-data-item');
|
||||||
if (item) item.remove();
|
if (item) item.remove();
|
||||||
@@ -505,9 +506,9 @@ function deleteStaleData(stackName, stalePath, btn) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function(e) {
|
.catch(function(e) {
|
||||||
alert('Hálózati hiba: ' + e.message);
|
showAlert('Hálózati hiba: ' + e.message);
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = '🗑️ Korábbi adatok törlése';
|
btn.textContent = 'Korábbi adatok törlése';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +520,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
|
|||||||
for (const pf of passwordFields) {
|
for (const pf of passwordFields) {
|
||||||
if (!pf.disabled && pf.value.trim() === '') {
|
if (!pf.disabled && pf.value.trim() === '') {
|
||||||
const label = pf.closest('.form-group').querySelector('label').textContent.trim();
|
const label = pf.closest('.form-group').querySelector('label').textContent.trim();
|
||||||
alert('Kötelező mező: ' + label + '\nHasználja a Generálás gombot vagy írjon be egy jelszót.');
|
showAlert('Kötelező mező: ' + label + '\nHasználja a Generálás gombot vagy írjon be egy jelszót.');
|
||||||
pf.focus();
|
pf.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -530,7 +531,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
|
|||||||
if (subdomainField && !subdomainField.disabled) {
|
if (subdomainField && !subdomainField.disabled) {
|
||||||
const sd = subdomainField.value.trim().toLowerCase();
|
const sd = subdomainField.value.trim().toLowerCase();
|
||||||
if (!sd || !/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(sd)) {
|
if (!sd || !/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(sd)) {
|
||||||
alert('Az aldomain csak kisbetűket, számokat és kötőjelet tartalmazhat, és nem kezdődhet/végződhet kötőjellel.');
|
showAlert('Az aldomain csak kisbetűket, számokat és kötőjelet tartalmazhat, és nem kezdődhet/végződhet kötőjellel.');
|
||||||
subdomainField.focus();
|
subdomainField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -541,7 +542,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
|
|||||||
for (const rf of requiredFields) {
|
for (const rf of requiredFields) {
|
||||||
if (!rf.disabled && rf.value.trim() === '') {
|
if (!rf.disabled && rf.value.trim() === '') {
|
||||||
const label = rf.closest('.form-group').querySelector('label').textContent.trim();
|
const label = rf.closest('.form-group').querySelector('label').textContent.trim();
|
||||||
alert('Kötelező mező: ' + label);
|
showAlert('Kötelező mező: ' + label);
|
||||||
rf.focus();
|
rf.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -592,7 +593,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
|
|||||||
});
|
});
|
||||||
var data = await resp.json();
|
var data = await resp.json();
|
||||||
if (!data.ok) {
|
if (!data.ok) {
|
||||||
alert('Hiba: ' + data.error);
|
showAlert('Hiba: ' + data.error);
|
||||||
btn.textContent = 'Telepítés indítása';
|
btn.textContent = 'Telepítés indítása';
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
return;
|
return;
|
||||||
@@ -669,7 +670,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Hálózati hiba: ' + err.message);
|
showAlert('Hálózati hiba: ' + err.message);
|
||||||
btn.textContent = 'Telepítés indítása';
|
btn.textContent = 'Telepítés indítása';
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@
|
|||||||
<title>{{.Title}} — Felhom.eu</title>
|
<title>{{.Title}} — Felhom.eu</title>
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
<meta name="csrf-token" content="{{.CSRFToken}}">
|
<meta name="csrf-token" content="{{.CSRFToken}}">
|
||||||
<script>function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}</script>
|
<script>
|
||||||
|
function csrfHeaders(){var el=document.querySelector('meta[name="csrf-token"]');return el?{'X-CSRF-Token':el.content}:{};}
|
||||||
|
function showAlert(msg){var o=document.createElement('div');o.className='modal-overlay';o.id='alert-modal';o.addEventListener('click',function(e){if(e.target===o)o.remove();});var c=document.createElement('div');c.className='modal-card';c.innerHTML='<h3>Üzenet</h3><pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-secondary);padding:.75rem;border-radius:.375rem;font-size:.85rem;max-height:60vh;overflow-y:auto;user-select:text;cursor:text">'+msg.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')+'</pre><div class="modal-actions"><button class="btn btn-primary" onclick="document.getElementById(\'alert-modal\').remove()">OK</button></div>';o.appendChild(c);document.body.appendChild(o);}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="sidebar">
|
<nav class="sidebar">
|
||||||
@@ -60,7 +63,7 @@
|
|||||||
var data = await resp.json();
|
var data = await resp.json();
|
||||||
if (data.ok && data.data && data.data.deployed) {
|
if (data.ok && data.data && data.data.deployed) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('Ez az alkalmazás már telepítve van.');
|
showAlert('Ez az alkalmazás már telepítve van.');
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -115,7 +118,7 @@
|
|||||||
});
|
});
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (!data.ok) {
|
if (!data.ok) {
|
||||||
alert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||||
btn.textContent = origText;
|
btn.textContent = origText;
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.classList.remove('loading');
|
btn.classList.remove('loading');
|
||||||
@@ -123,7 +126,7 @@
|
|||||||
}
|
}
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Hálózati hiba: ' + err.message);
|
showAlert('Hálózati hiba: ' + err.message);
|
||||||
btn.textContent = origText;
|
btn.textContent = origText;
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.classList.remove('loading');
|
btn.classList.remove('loading');
|
||||||
@@ -197,12 +200,12 @@
|
|||||||
removedInfo + preservedInfo +
|
removedInfo + preservedInfo +
|
||||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||||
} else {
|
} else {
|
||||||
alert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Törlés';
|
btn.textContent = 'Törlés';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Hálózati hiba: ' + err.message);
|
showAlert('Hálózati hiba: ' + err.message);
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Törlés';
|
btn.textContent = 'Törlés';
|
||||||
}
|
}
|
||||||
@@ -312,12 +315,12 @@
|
|||||||
removedInfo + preservedInfo +
|
removedInfo + preservedInfo +
|
||||||
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
'<div class="modal-actions"><button class="btn btn-primary" onclick="window.location.href=\'/stacks\'">Bezárás</button></div>';
|
||||||
} else {
|
} else {
|
||||||
alert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
showAlert('Hiba: ' + (data.error || 'Ismeretlen hiba'));
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Eltávolítás';
|
btn.textContent = 'Eltávolítás';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alert('Hálózati hiba: ' + err.message);
|
showAlert('Hálózati hiba: ' + err.message);
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
btn.textContent = 'Eltávolítás';
|
btn.textContent = 'Eltávolítás';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ function pollUntilBack() {
|
|||||||
{{else}}
|
{{else}}
|
||||||
{{if .IsDefault}}<span class="badge state-green">Alapértelmezett</span>{{end}}
|
{{if .IsDefault}}<span class="badge state-green">Alapértelmezett</span>{{end}}
|
||||||
{{if .Schedulable}}<span class="badge" style="background:rgba(0,136,204,0.15);color:var(--accent-light)">Aktív</span>{{else}}<span class="badge state-gray">Inaktív</span>{{end}}
|
{{if .Schedulable}}<span class="badge" style="background:rgba(0,136,204,0.15);color:var(--accent-light)">Aktív</span>{{else}}<span class="badge state-gray">Inaktív</span>{{end}}
|
||||||
|
{{if .IsUSB}}<span class="badge" style="background:rgba(255,165,0,0.15);color:var(--yellow)">USB</span>{{end}}
|
||||||
{{if not .IsMounted}}<span class="badge badge-warn">Rendszermeghajtón</span>{{end}}
|
{{if not .IsMounted}}<span class="badge badge-warn">Rendszermeghajtón</span>{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
{{if .Meta.Resources.MemRequest}}<span class="meta-badge">~{{.Meta.Resources.MemRequest}}</span>{{end}}
|
{{if .Meta.Resources.MemRequest}}<span class="meta-badge">~{{.Meta.Resources.MemRequest}}</span>{{end}}
|
||||||
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{end}}
|
{{if .Meta.Resources.PiCompatible}}<span class="meta-badge meta-badge-ok">Pi kompatibilis</span>{{end}}
|
||||||
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge">HDD szükséges</span>{{end}}
|
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge">HDD szükséges</span>{{end}}
|
||||||
|
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">Magyar felület</span>{{end}}
|
||||||
{{if and .Deployed (index $.StorageLabels .Name)}}<span class="meta-badge meta-badge-storage" title="Adattároló: {{index $.StorageLabels .Name}}">💾 {{index $.StorageLabels .Name}}</span>{{end}}
|
{{if and .Deployed (index $.StorageLabels .Name)}}<span class="meta-badge meta-badge-storage" title="Adattároló: {{index $.StorageLabels .Name}}">💾 {{index $.StorageLabels .Name}}</span>{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user