Integrate filesystem switches into the reconcile dashboard card
This commit is contained in:
1 parent
a515828879
commit
e10bddbaff
2 files changed
+26
-5
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user