Remaining data—
Average net progress—
Estimated time remaining—
@@ -78,14 +84,18 @@ 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.shadowRoot.querySelectorAll('.switch-button').forEach(button => {
+ button.addEventListener('click', () => this._toggleSwitch(button.dataset.entity));
+ });
+ this._updateSwitches();
this._buildGraphs();
if (this._hass) this._load();
}
set hass(hass) {
const first = !this._hass;
this._hass = hass;
- [...this._graphs, ...this._switches].forEach(card => {card.hass = hass;});
+ this._graphs.forEach(card => {card.hass = hass;});
+ this._updateSwitches();
const entity = hass.states[this._entity()];
if (entity && !Number.isFinite(Number(entity.state))) {
++this._request;
@@ -98,24 +108,35 @@ class BcachefsReconcileCard extends (globalThis.HTMLElement || class {}) {
if (this._hass && this._config) this._load();
}
disconnectedCallback() { clearInterval(this._timer); ++this._request; }
- getCardSize() {return 16;}
+ getCardSize() {return 14;}
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);
+ _updateSwitches() {
+ this.shadowRoot.querySelectorAll('.switch-button').forEach(button => {
+ const state = this._hass?.states[button.dataset.entity]?.state;
+ const available = state === 'on' || state === 'off';
+ const pending = this._pendingSwitches.has(button.dataset.entity);
+ button.setAttribute('aria-pressed', String(state === 'on'));
+ button.setAttribute('aria-busy', String(pending));
+ button.disabled = !available || pending;
+ button.textContent = `${button.dataset.name} · ${pending ? 'Updating…' : !available ? 'Unavailable' : state === 'on' ? 'On' : 'Off'}`;
+ button.title = available ? `Turn ${state === 'on' ? 'off' : 'on'} ${button.dataset.name}` : `${button.dataset.name} is unavailable`;
+ });
+ }
+ async _toggleSwitch(entity) {
+ const state = this._hass?.states[entity]?.state;
+ if (!['on', 'off'].includes(state) || this._pendingSwitches.has(entity)) return;
+ this._pendingSwitches.add(entity);
+ this.shadowRoot.querySelector('#control-error').textContent = '';
+ this._updateSwitches();
+ try {
+ await this._hass.callService('switch', state === 'on' ? 'turn_off' : 'turn_on', {entity_id: entity});
+ } catch (error) {
+ this.shadowRoot.querySelector('#control-error').textContent = `Could not change switch: ${error.message || error}`;
+ } finally {
+ this._pendingSwitches.delete(entity);
+ this._updateSwitches();
+ }
}
async _buildGraphs() {
const generation = this._graphGeneration = (this._graphGeneration || 0) + 1;