Integrate filesystem switches into the reconcile dashboard card

This commit is contained in:
feedc0de committed 2026-11-02 11:45:37 +01:00
1 parent a515828879
commit e10bddbaff
2 files changed
+26 -5

No files matched your search

+3 -1
View File
@@ -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.
+23 -4
View File
@@ -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 = `<style>
:host{display:block}ha-card{padding:16px}.controls{display:flex;gap:16px;flex-wrap:wrap}
h2{margin:0 0 16px;font-size:20px;font-weight:500}label{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--secondary-text-color)}
.switches{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-bottom:20px}h2{margin:0 0 16px;font-size:20px;font-weight:500}label{display:flex;flex-direction:column;gap:6px;font-size:13px;color:var(--secondary-text-color)}
select{font:inherit;color:var(--primary-text-color);background:var(--card-background-color);border:1px solid var(--divider-color);border-radius:8px;padding:10px;min-width:160px}
.summary{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin:20px 0 12px}.metric{background:var(--secondary-background-color);padding:12px;border-radius:8px}
.metric span{display:block;font-size:12px;color:var(--secondary-text-color)}.metric strong{display:block;margin-top:8px;font-size:22px;font-weight:500;overflow-wrap:anywhere}
.note{font-size:13px;color:var(--secondary-text-color);line-height:1.5;margin:6px 0}.graphs{display:grid;gap:16px;margin-top:16px}
@media(max-width:450px){.summary{grid-template-columns:1fr}.metric strong{font-size:20px}}
@media(max-width:450px){.switches{grid-template-columns:1fr}.summary{grid-template-columns:1fr}.metric strong{font-size:20px}}
</style><ha-card><h2>Pending reconcile work</h2>
<div class="switches" aria-label="Filesystem controls"></div>
<div class="controls"><label>History and calculation window<select id="range"></select></label>
<label>Estimate for<select id="field"><option value="target_data">Target data</option><option value="erasure_code_data">Erasure-code data</option></select></label></div>
<div class="summary"><div class="metric"><span>Remaining data</span><strong id="remaining">—</strong></div>
@@ -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 {