This commit is contained in:
2026-01-17 08:13:22 +00:00
parent e10624df6b
commit 24933e980d

View File

@@ -29,6 +29,7 @@ class BahmcloudStorePanel extends HTMLElement {
this._installingRepoId = null;
this._updatingRepoId = null;
this._uninstallingRepoId = null;
this._restartRequired = false;
this._lastActionMsg = null;
}
@@ -141,6 +142,36 @@ class BahmcloudStorePanel extends HTMLElement {
}
}
async _uninstallRepo(repoId) {
if (!repoId) return;
if (this._installingRepoId || this._updatingRepoId || this._uninstallingRepoId) return;
const r = this._repoById(repoId);
const name = this._safeText(r?.name) || repoId;
const ok = window.confirm(`Uninstall "${name}"?\n\nThis will delete the integration folder(s) from /config/custom_components. A restart will be required.`);
if (!ok) return;
this._uninstallingRepoId = repoId;
this._lastActionMsg = null;
this.requestUpdate();
try {
const resp = await this._hass.callApi("post", `bcs/uninstall?repo_id=${encodeURIComponent(repoId)}`, {});
if (resp && resp.ok) {
this._restartRequired = !!resp.restart_required;
this._lastActionMsg = "Uninstalled. Restart required.";
} else {
this._lastActionMsg = (resp && resp.message) ? String(resp.message) : "Uninstall failed.";
}
} catch (e) {
this._lastActionMsg = e && e.message ? e.message : "Uninstall failed.";
} finally {
this._uninstallingRepoId = null;
await this._load();
}
}
async _restartHA() {
if (!this._hass) return;
try {
@@ -436,15 +467,15 @@ class BahmcloudStorePanel extends HTMLElement {
<div class="mobilebar">
<div class="left">
<div class="iconbtn" id="menuBtn" title="Menu"></div>
<div class="iconbtn" id="backBtn" title="Back"></div>
<div class="iconbtn" id="menuBtn" title="Menu"><EFBFBD></div>
<div class="iconbtn" id="backBtn" title="Back"><EFBFBD></div>
<div>
<div style="font-weight:700;">Bahmcloud Store</div>
<div class="muted small" id="subtitle">Store</div>
</div>
</div>
<div class="right">
<div class="iconbtn" id="refreshBtn" title="Refresh"></div>
<div class="iconbtn" id="refreshBtn" title="Refresh"><EFBFBD></div>
</div>
</div>
@@ -506,7 +537,7 @@ class BahmcloudStorePanel extends HTMLElement {
: "";
if (this._loading) {
content.innerHTML = `${err}<div class="card">Loading</div>`;
content.innerHTML = `${err}<div class="card">Loading<EFBFBD></div>`;
fabs.innerHTML = "";
return;
}
@@ -608,7 +639,7 @@ class BahmcloudStorePanel extends HTMLElement {
if (updateAvailable) badges.push("Update");
const badgeHtml = badges.length
? `<div class="badge">${this._esc(badges.join(" · "))}</div>`
? `<div class="badge">${this._esc(badges.join(" "))}</div>`
: `<div class="badge">${this._esc(this._safeText(r?.provider || "repo"))}</div>`;
return `
@@ -628,7 +659,7 @@ class BahmcloudStorePanel extends HTMLElement {
return `
<div class="filters">
<input id="q" placeholder="Search" value="${this._esc(this._search)}" />
<input id="q" placeholder="Search<EFBFBD>" value="${this._esc(this._search)}" />
<select id="cat">
<option value="all">All categories</option>
${categories.map((c) => `<option value="${this._esc(c)}" ${this._category === c ? "selected" : ""}>${this._esc(c)}</option>`).join("")}
@@ -640,7 +671,7 @@ class BahmcloudStorePanel extends HTMLElement {
</select>
</div>
<div class="muted small">Version: ${this._esc(this._data.version || "-")} · Repositories: ${repos.length}</div>
<div class="muted small">Version: ${this._esc(this._data.version || "-")} Repositories: ${repos.length}</div>
<div class="grid" style="margin-top:12px;">
${cards || `<div class="card">No repositories found.</div>`}
@@ -713,7 +744,7 @@ class BahmcloudStorePanel extends HTMLElement {
].filter(Boolean);
const readmeBlock = this._readmeLoading
? `<div class="card">Loading README</div>`
? `<div class="card">Loading README<EFBFBD></div>`
: this._readmeText
? `
<div class="card">
@@ -748,12 +779,14 @@ class BahmcloudStorePanel extends HTMLElement {
const busyInstall = this._installingRepoId === repoId;
const busyUpdate = this._updatingRepoId === repoId;
const busy = busyInstall || busyUpdate;
const busyUninstall = this._uninstallingRepoId === repoId;
const busy = busyInstall || busyUpdate || busyUninstall;
const updateAvailable = installed && !!latestVersion && (!installedVersion || latestVersion !== installedVersion);
const installBtn = `<button class="primary" id="btnInstall" ${installed || busy ? "disabled" : ""}>${busyInstall ? "Installing" : installed ? "Installed" : "Install"}</button>`;
const updateBtn = `<button class="primary" id="btnUpdate" ${!updateAvailable || busy ? "disabled" : ""}>${busyUpdate ? "Updating" : updateAvailable ? "Update" : "Up to date"}</button>`;
const installBtn = `<button class="primary" id="btnInstall" ${installed || busy ? "disabled" : ""}>${busyInstall ? "Installing<EFBFBD>" : installed ? "Installed" : "Install"}</button>`;
const updateBtn = `<button class="primary" id="btnUpdate" ${!updateAvailable || busy ? "disabled" : ""}>${busyUpdate ? "Updating<EFBFBD>" : updateAvailable ? "Update" : "Up to date"}</button>`;
const uninstallBtn = `<button id="btnUninstall" ${!installed || busy ? "disabled" : ""}>${busyUninstall ? "Uninstalling鈥<67>" : "Uninstall"}</button>`;
const restartHint = this._restartRequired
? `
@@ -777,7 +810,7 @@ class BahmcloudStorePanel extends HTMLElement {
<div>
<div><strong style="font-size:16px;">${this._esc(name)}</strong></div>
<div class="muted">${this._esc(desc)}</div>
<div class="muted small" style="margin-top:8px;">${this._esc(infoBits.join(" · "))}</div>
<div class="muted small" style="margin-top:8px;">${this._esc(infoBits.join(" "))}</div>
<div class="muted small" style="margin-top:8px;">
<a href="${this._esc(url)}" target="_blank" rel="noreferrer">Open repository</a>
</div>
@@ -805,6 +838,7 @@ class BahmcloudStorePanel extends HTMLElement {
<div class="row" style="margin-top:14px; gap:10px; flex-wrap:wrap;">
${installBtn}
${updateBtn}
${uninstallBtn}
</div>
${restartHint}
@@ -820,6 +854,7 @@ class BahmcloudStorePanel extends HTMLElement {
// Always wire action buttons (even if README is already loaded)
const btnInstall = root.getElementById("btnInstall");
const btnUpdate = root.getElementById("btnUpdate");
const btnUninstall = root.getElementById("btnUninstall");
const btnRestart = root.getElementById("btnRestart");
if (btnInstall) {
@@ -836,6 +871,13 @@ class BahmcloudStorePanel extends HTMLElement {
});
}
if (btnUninstall) {
btnUninstall.addEventListener("click", () => {
if (btnUninstall.disabled) return;
if (this._detailRepoId) this._uninstallRepo(this._detailRepoId);
});
}
if (btnRestart) {
btnRestart.addEventListener("click", () => this._restartHA());
}
@@ -848,7 +890,7 @@ class BahmcloudStorePanel extends HTMLElement {
mount.innerHTML = this._readmeHtml;
this._postprocessRenderedMarkdown(mount);
} else {
mount.innerHTML = `<div class="muted">Rendered HTML not available. Use “Show raw Markdown.</div>`;
mount.innerHTML = `<div class="muted">Rendered HTML not available. Use 鈥淪how raw Markdown<EFBFBD>.</div>`;
}
} else {
mount.innerHTML = "";
@@ -883,10 +925,10 @@ class BahmcloudStorePanel extends HTMLElement {
return `
<div class="fabs">
<button class="fabbtn primary" id="fabOpen" title="Open repository"></button>
<button class="fabbtn" id="fabReload" title="Reload README"></button>
<button class="fabbtn" id="fabInstall" title="${installDisabled ? (installed ? "Already installed" : "Installing") : "Install"}" ${installDisabled ? "disabled" : ""}></button>
<button class="fabbtn" id="fabUpdate" title="${updateDisabled ? (!installed ? "Not installed" : "No update available") : "Update"}" ${updateDisabled ? "disabled" : ""}></button>
<button class="fabbtn primary" id="fabOpen" title="Open repository"><EFBFBD></button>
<button class="fabbtn" id="fabReload" title="Reload README"><EFBFBD></button>
<button class="fabbtn" id="fabInstall" title="${installDisabled ? (installed ? "Already installed" : "Installing<EFBFBD>") : "Install"}" ${installDisabled ? "disabled" : ""}><EFBFBD></button>
<button class="fabbtn" id="fabUpdate" title="${updateDisabled ? (!installed ? "Not installed" : "No update available") : "Update"}" ${updateDisabled ? "disabled" : ""}><EFBFBD></button>
<button class="fabbtn" id="fabInfo" title="About">i</button>
</div>
`;