diff --git a/README.md b/README.md index 7e588e2..7cd257c 100644 --- a/README.md +++ b/README.md @@ -213,7 +213,9 @@ History begins when the sensors are installed; no InfluxDB data is imported. `homeassistant/bcachefs-reconcile-card.mjs` is a custom dashboard card using the already-installed ApexCharts Card for graphs and Home Assistant's authenticated history WebSocket API for estimates. The host's existing -`/dashboard-bcachefs/0` dashboard includes this card alongside its switches. +`/dashboard-bcachefs/0` dashboard includes this card with its Reconcile and Copy GC switches integrated +above the history controls. The embedded native Home Assistant tiles preserve +live state, unavailable handling, and the entity details dialog. - One selector applies 3h, 6h, 12h, 24h, 2d or 7d to both graphs and calculations. - Another selector chooses `target_data` or `erasure_code_data` for the estimate. diff --git a/homeassistant/bcachefs-reconcile-card.mjs b/homeassistant/bcachefs-reconcile-card.mjs index ebb241d..30da705 100644 --- a/homeassistant/bcachefs-reconcile-card.mjs +++ b/homeassistant/bcachefs-reconcile-card.mjs @@ -49,19 +49,21 @@ class BcachefsReconcileCard extends (globalThis.HTMLElement || class {}) { this._field = 'target_data'; this._request = 0; this._graphs = []; + this._switches = []; } setConfig(config) { this._config = {entity_prefix: 'sensor.bcachefs_reconcile_', ...config}; this._hours = RANGES.some(([hours]) => hours === config.hours) ? config.hours : this._hours; this.shadowRoot.innerHTML = `

Pending reconcile work

+
Remaining data—
@@ -76,13 +78,14 @@ class BcachefsReconcileCard extends (globalThis.HTMLElement || class {}) { this.shadowRoot.querySelector('#field').value = this._field; select.addEventListener('change', () => {this._hours = Number(select.value); this._buildGraphs(); this._load();}); this.shadowRoot.querySelector('#field').addEventListener('change', event => {this._field = event.target.value; this._load();}); + this._buildSwitches(); this._buildGraphs(); if (this._hass) this._load(); } set hass(hass) { const first = !this._hass; this._hass = hass; - this._graphs.forEach(graph => {graph.hass = hass;}); + [...this._graphs, ...this._switches].forEach(card => {card.hass = hass;}); const entity = hass.states[this._entity()]; if (entity && !Number.isFinite(Number(entity.state))) { ++this._request; @@ -95,9 +98,25 @@ class BcachefsReconcileCard extends (globalThis.HTMLElement || class {}) { if (this._hass && this._config) this._load(); } disconnectedCallback() { clearInterval(this._timer); ++this._request; } - getCardSize() {return 14;} + getCardSize() {return 16;} getGridOptions() {return {columns: 'full', rows: 'auto'};} _entity() {return `${this._config?.entity_prefix || 'sensor.bcachefs_reconcile_'}${this._field}`;} + async _buildSwitches() { + const generation = this._switchGeneration = (this._switchGeneration || 0) + 1; + const helpers = await window.loadCardHelpers(); + if (generation !== this._switchGeneration) return; + this._switches = [ + ['switch.bcachefs_reconcile_enabled', 'Reconcile', 'mdi:sync'], + ['switch.bcachefs_copygc_enabled', 'Copy GC', 'mdi:recycle'], + ].map(([entity, name, icon]) => helpers.createCardElement({ + type: 'tile', entity, name, icon, + tap_action: {action: 'more-info'}, + icon_tap_action: {action: 'toggle'}, + features: [{type: 'toggle'}], features_position: 'inline', + })); + this._switches.forEach(card => {if (this._hass) card.hass = this._hass;}); + this.shadowRoot.querySelector('.switches').replaceChildren(...this._switches); + } async _buildGraphs() { const generation = this._graphGeneration = (this._graphGeneration || 0) + 1; try {