mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-13 20:36:35 +00:00
Merge branch 'dev' into toggle_group_dialog
This commit is contained in:
commit
134681b4c9
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -11,7 +11,7 @@ body:
|
||||
|
||||
**Please do not report issues for custom cards.**
|
||||
|
||||
[fr]: https://github.com/home-assistant/frontend/discussions
|
||||
[fr]: https://github.com/orgs/home-assistant/discussions
|
||||
[releases]: https://github.com/home-assistant/home-assistant/releases
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,7 +1,7 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Request a feature for the UI / Dashboards
|
||||
url: https://github.com/home-assistant/frontend/discussions/category_choices
|
||||
url: https://github.com/orgs/home-assistant/discussions
|
||||
about: Request a new feature for the Home Assistant frontend.
|
||||
- name: Report a bug that is NOT related to the UI / Dashboards
|
||||
url: https://github.com/home-assistant/core/issues
|
||||
|
53
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
53
.github/ISSUE_TEMPLATE/task.yml
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
name: Task
|
||||
description: For staff only - Create a task
|
||||
type: Task
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## ⚠️ RESTRICTED ACCESS
|
||||
|
||||
**This form is restricted to Open Home Foundation staff and authorized contributors only.**
|
||||
|
||||
If you are a community member wanting to contribute, please:
|
||||
- For bug reports: Use the [bug report form](https://github.com/home-assistant/frontend/issues/new?template=bug_report.yml)
|
||||
- For feature requests: Submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)
|
||||
|
||||
---
|
||||
|
||||
### For authorized contributors
|
||||
|
||||
Use this form to create tasks for development work, improvements, or other actionable items that need to be tracked.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Provide a clear and detailed description of the task that needs to be accomplished.
|
||||
|
||||
Be specific about what needs to be done, why it's important, and any constraints or requirements.
|
||||
placeholder: |
|
||||
Describe the task, including:
|
||||
- What needs to be done
|
||||
- Why this task is needed
|
||||
- Expected outcome
|
||||
- Any constraints or requirements
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: additional_context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: |
|
||||
Any additional information, links, research, or context that would be helpful.
|
||||
|
||||
Include links to related issues, research, prototypes, roadmap opportunities etc.
|
||||
placeholder: |
|
||||
- Roadmap opportunity: [link]
|
||||
- Epic: [link]
|
||||
- Feature request: [link]
|
||||
- Technical design documents: [link]
|
||||
- Prototype/mockup: [link]
|
||||
- Dependencies: [links]
|
||||
validations:
|
||||
required: false
|
58
.github/workflows/restrict-task-creation.yml
vendored
Normal file
58
.github/workflows/restrict-task-creation.yml
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
name: Restrict task creation
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
check-authorization:
|
||||
runs-on: ubuntu-latest
|
||||
# Only run if this is a Task issue type (from the issue form)
|
||||
if: github.event.issue.issue_type == 'Task'
|
||||
steps:
|
||||
- name: Check if user is authorized
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueAuthor = context.payload.issue.user.login;
|
||||
|
||||
// Check if user is an organization member
|
||||
try {
|
||||
await github.rest.orgs.checkMembershipForUser({
|
||||
org: 'home-assistant',
|
||||
username: issueAuthor
|
||||
});
|
||||
console.log(`✅ ${issueAuthor} is an organization member`);
|
||||
return; // Authorized
|
||||
} catch (error) {
|
||||
console.log(`❌ ${issueAuthor} is not authorized to create Task issues`);
|
||||
}
|
||||
|
||||
// Close the issue with a comment
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: `Hi @${issueAuthor}, thank you for your contribution!\n\n` +
|
||||
`Task issues are restricted to Open Home Foundation staff and authorized contributors.\n\n` +
|
||||
`If you would like to:\n` +
|
||||
`- Report a bug: Please use the [bug report form](https://github.com/home-assistant/frontend/issues/new?template=bug_report.yml)\n` +
|
||||
`- Request a feature: Please submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)\n\n` +
|
||||
`If you believe you should have access to create Task issues, please contact the maintainers.`
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state: 'closed'
|
||||
});
|
||||
|
||||
// Add a label to indicate this was auto-closed
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
labels: ['auto-closed']
|
||||
});
|
@ -99,7 +99,7 @@
|
||||
"barcode-detector": "3.0.5",
|
||||
"color-name": "2.0.0",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.43.0",
|
||||
"core-js": "3.44.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"date-fns": "4.1.0",
|
||||
"date-fns-tz": "3.2.0",
|
||||
@ -136,7 +136,7 @@
|
||||
"superstruct": "2.0.2",
|
||||
"tinykeys": "3.0.0",
|
||||
"ua-parser-js": "2.0.4",
|
||||
"vis-data": "7.1.9",
|
||||
"vis-data": "7.1.10",
|
||||
"vue": "2.7.16",
|
||||
"vue2-daterange-picker": "0.6.8",
|
||||
"weekstart": "2.0.0",
|
||||
@ -158,7 +158,7 @@
|
||||
"@octokit/auth-oauth-device": "8.0.1",
|
||||
"@octokit/plugin-retry": "8.0.1",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@rsdoctor/rspack-plugin": "1.1.7",
|
||||
"@rsdoctor/rspack-plugin": "1.1.8",
|
||||
"@rspack/cli": "1.4.4",
|
||||
"@rspack/core": "1.4.4",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
|
@ -433,6 +433,7 @@ export class HaFloorPicker extends LitElement {
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._setValue(value);
|
||||
|
@ -36,6 +36,8 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const PROGRAMMITIC_FIT_DELAY = 250;
|
||||
|
||||
const getEntityId = (entity: string | HaMapEntity): string =>
|
||||
typeof entity === "string" ? entity : entity.entity_id;
|
||||
|
||||
@ -113,14 +115,33 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
private _clickCount = 0;
|
||||
|
||||
private _isProgrammaticFit = false;
|
||||
|
||||
private _pauseAutoFit = false;
|
||||
|
||||
public connectedCallback(): void {
|
||||
this._pauseAutoFit = false;
|
||||
document.addEventListener("visibilitychange", this._handleVisibilityChange);
|
||||
this._handleVisibilityChange();
|
||||
super.connectedCallback();
|
||||
this._loadMap();
|
||||
this._attachObserver();
|
||||
}
|
||||
|
||||
private _handleVisibilityChange = async () => {
|
||||
if (!document.hidden) {
|
||||
setTimeout(() => {
|
||||
this._pauseAutoFit = false;
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
public disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
this._handleVisibilityChange
|
||||
);
|
||||
if (this.leafletMap) {
|
||||
this.leafletMap.remove();
|
||||
this.leafletMap = undefined;
|
||||
@ -145,7 +166,7 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
if (changedProps.has("_loaded") || changedProps.has("entities")) {
|
||||
this._drawEntities();
|
||||
autoFitRequired = true;
|
||||
autoFitRequired = !this._pauseAutoFit;
|
||||
} else if (this._loaded && oldHass && this.entities) {
|
||||
// Check if any state has changed
|
||||
for (const entity of this.entities) {
|
||||
@ -154,7 +175,7 @@ export class HaMap extends ReactiveElement {
|
||||
this.hass!.states[getEntityId(entity)]
|
||||
) {
|
||||
this._drawEntities();
|
||||
autoFitRequired = true;
|
||||
autoFitRequired = !this._pauseAutoFit;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -178,7 +199,11 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
if (changedProps.has("zoom")) {
|
||||
this._isProgrammaticFit = true;
|
||||
this.leafletMap!.setZoom(this.zoom);
|
||||
setTimeout(() => {
|
||||
this._isProgrammaticFit = false;
|
||||
}, PROGRAMMITIC_FIT_DELAY);
|
||||
}
|
||||
|
||||
if (
|
||||
@ -234,13 +259,30 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
this._clickCount++;
|
||||
});
|
||||
this.leafletMap.on("zoomstart", () => {
|
||||
if (!this._isProgrammaticFit) {
|
||||
this._pauseAutoFit = true;
|
||||
}
|
||||
});
|
||||
this.leafletMap.on("movestart", () => {
|
||||
if (!this._isProgrammaticFit) {
|
||||
this._pauseAutoFit = true;
|
||||
}
|
||||
});
|
||||
this._loaded = true;
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
public fitMap(options?: { zoom?: number; pad?: number }): void {
|
||||
public fitMap(options?: {
|
||||
zoom?: number;
|
||||
pad?: number;
|
||||
unpause_autofit?: boolean;
|
||||
}): void {
|
||||
if (options?.unpause_autofit) {
|
||||
this._pauseAutoFit = false;
|
||||
}
|
||||
if (!this.leafletMap || !this.Leaflet || !this.hass) {
|
||||
return;
|
||||
}
|
||||
@ -250,6 +292,7 @@ export class HaMap extends ReactiveElement {
|
||||
!this._mapFocusZones.length &&
|
||||
!this.layers?.length
|
||||
) {
|
||||
this._isProgrammaticFit = true;
|
||||
this.leafletMap.setView(
|
||||
new this.Leaflet.LatLng(
|
||||
this.hass.config.latitude,
|
||||
@ -257,6 +300,9 @@ export class HaMap extends ReactiveElement {
|
||||
),
|
||||
options?.zoom || this.zoom
|
||||
);
|
||||
setTimeout(() => {
|
||||
this._isProgrammaticFit = false;
|
||||
}, PROGRAMMITIC_FIT_DELAY);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -277,8 +323,11 @@ export class HaMap extends ReactiveElement {
|
||||
});
|
||||
|
||||
bounds = bounds.pad(options?.pad ?? 0.5);
|
||||
|
||||
this._isProgrammaticFit = true;
|
||||
this.leafletMap.fitBounds(bounds, { maxZoom: options?.zoom || this.zoom });
|
||||
setTimeout(() => {
|
||||
this._isProgrammaticFit = false;
|
||||
}, PROGRAMMITIC_FIT_DELAY);
|
||||
}
|
||||
|
||||
public fitBounds(
|
||||
@ -291,7 +340,11 @@ export class HaMap extends ReactiveElement {
|
||||
const bounds = this.Leaflet.latLngBounds(boundingbox).pad(
|
||||
options?.pad ?? 0.5
|
||||
);
|
||||
this._isProgrammaticFit = true;
|
||||
this.leafletMap.fitBounds(bounds, { maxZoom: options?.zoom || this.zoom });
|
||||
setTimeout(() => {
|
||||
this._isProgrammaticFit = false;
|
||||
}, PROGRAMMITIC_FIT_DELAY);
|
||||
}
|
||||
|
||||
private _drawLayers(prevLayers: Layer[] | undefined): void {
|
||||
|
@ -1109,21 +1109,31 @@ export const computeConsumptionSingle = (data: {
|
||||
export const formatConsumptionShort = (
|
||||
hass: HomeAssistant,
|
||||
consumption: number | null,
|
||||
unit: string
|
||||
unit: string,
|
||||
targetUnit?: string
|
||||
): string => {
|
||||
if (!consumption) {
|
||||
return `0 ${unit}`;
|
||||
}
|
||||
const units = ["Wh", "kWh", "MWh", "GWh", "TWh"];
|
||||
let pickedUnit = unit;
|
||||
let val = consumption;
|
||||
let val = consumption || 0;
|
||||
let targetUnitIndex = -1;
|
||||
if (targetUnit) {
|
||||
targetUnitIndex = units.findIndex((u) => u === targetUnit);
|
||||
}
|
||||
let unitIndex = units.findIndex((u) => u === unit);
|
||||
if (unitIndex >= 0) {
|
||||
while (Math.abs(val) < 1 && unitIndex > 0) {
|
||||
while (
|
||||
targetUnitIndex > -1
|
||||
? targetUnitIndex < unitIndex
|
||||
: Math.abs(val) < 1 && unitIndex > 0
|
||||
) {
|
||||
val *= 1000;
|
||||
unitIndex--;
|
||||
}
|
||||
while (Math.abs(val) >= 1000 && unitIndex < units.length - 1) {
|
||||
while (
|
||||
targetUnitIndex > -1
|
||||
? targetUnitIndex > unitIndex
|
||||
: Math.abs(val) >= 1000 && unitIndex < units.length - 1
|
||||
) {
|
||||
val /= 1000;
|
||||
unitIndex++;
|
||||
}
|
||||
|
@ -438,7 +438,10 @@ class DataEntryFlowDialog extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
this._loading = "loading_step";
|
||||
const delayedLoading = setTimeout(() => {
|
||||
// only show loading for slow steps to avoid flickering
|
||||
this._loading = "loading_step";
|
||||
}, 250);
|
||||
let _step: DataEntryFlowStep;
|
||||
try {
|
||||
_step = await step;
|
||||
@ -452,6 +455,7 @@ class DataEntryFlowDialog extends LitElement {
|
||||
});
|
||||
return;
|
||||
} finally {
|
||||
clearTimeout(delayedLoading);
|
||||
this._loading = undefined;
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ import { formatShortDateTime } from "../../../common/datetime/format_date_time";
|
||||
import { storage } from "../../../common/decorators/storage";
|
||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDeviceNameDisplay } from "../../../common/entity/compute_device_name";
|
||||
import { computeFloorName } from "../../../common/entity/compute_floor_name";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import {
|
||||
PROTOCOL_INTEGRATIONS,
|
||||
@ -424,6 +425,18 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
(lbl) => labelReg!.find((label) => label.label_id === lbl)!
|
||||
);
|
||||
|
||||
let floorName = "—";
|
||||
if (
|
||||
device.area_id &&
|
||||
areas[device.area_id]?.floor_id &&
|
||||
this.hass.floors
|
||||
) {
|
||||
const floorId = areas[device.area_id].floor_id;
|
||||
if (this.hass.floors[floorId!]) {
|
||||
floorName = computeFloorName(this.hass.floors[floorId!]);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...device,
|
||||
name: computeDeviceNameDisplay(
|
||||
@ -441,6 +454,7 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
device.area_id && areas[device.area_id]
|
||||
? areas[device.area_id].name
|
||||
: "—",
|
||||
floor: floorName,
|
||||
integration: deviceEntries.length
|
||||
? deviceEntries
|
||||
.map(
|
||||
@ -524,6 +538,14 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) {
|
||||
groupable: true,
|
||||
minWidth: "120px",
|
||||
},
|
||||
floor: {
|
||||
title: localize("ui.panel.config.devices.data_table.floor"),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
groupable: true,
|
||||
minWidth: "120px",
|
||||
defaultHidden: true,
|
||||
},
|
||||
integration: {
|
||||
title: localize("ui.panel.config.devices.data_table.integration"),
|
||||
sortable: true,
|
||||
|
@ -255,6 +255,20 @@ class HuiEnergyDistrubutionCard
|
||||
(batteryFromGrid || 0) +
|
||||
(batteryToGrid || 0);
|
||||
|
||||
// Coerce all energy numbers to the same unit (the biggest)
|
||||
const maxEnergy = Math.max(
|
||||
lowCarbonEnergy || 0,
|
||||
totalSolarProduction || 0,
|
||||
returnedToGrid || 0,
|
||||
totalFromGrid || 0,
|
||||
totalHomeConsumption,
|
||||
totalBatteryIn || 0,
|
||||
totalBatteryOut || 0
|
||||
);
|
||||
const targetEnergyUnit = formatConsumptionShort(this.hass, maxEnergy, "kWh")
|
||||
.split(" ")
|
||||
.pop();
|
||||
|
||||
return html`
|
||||
<ha-card .header=${this._config.title}>
|
||||
<div class="card-content">
|
||||
@ -281,7 +295,8 @@ class HuiEnergyDistrubutionCard
|
||||
${formatConsumptionShort(
|
||||
this.hass,
|
||||
lowCarbonEnergy,
|
||||
"kWh"
|
||||
"kWh",
|
||||
targetEnergyUnit
|
||||
)}
|
||||
</a>
|
||||
<svg width="80" height="30">
|
||||
@ -300,7 +315,8 @@ class HuiEnergyDistrubutionCard
|
||||
${formatConsumptionShort(
|
||||
this.hass,
|
||||
totalSolarProduction,
|
||||
"kWh"
|
||||
"kWh",
|
||||
targetEnergyUnit
|
||||
)}
|
||||
</div>
|
||||
</div>`
|
||||
@ -396,7 +412,8 @@ class HuiEnergyDistrubutionCard
|
||||
>${formatConsumptionShort(
|
||||
this.hass,
|
||||
returnedToGrid,
|
||||
"kWh"
|
||||
"kWh",
|
||||
targetEnergyUnit
|
||||
)}
|
||||
</span>`
|
||||
: ""}
|
||||
@ -409,7 +426,8 @@ class HuiEnergyDistrubutionCard
|
||||
: ""}${formatConsumptionShort(
|
||||
this.hass,
|
||||
totalFromGrid,
|
||||
"kWh"
|
||||
"kWh",
|
||||
targetEnergyUnit
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
@ -432,7 +450,8 @@ class HuiEnergyDistrubutionCard
|
||||
${formatConsumptionShort(
|
||||
this.hass,
|
||||
totalHomeConsumption,
|
||||
"kWh"
|
||||
"kWh",
|
||||
targetEnergyUnit
|
||||
)}
|
||||
${homeSolarCircumference !== undefined ||
|
||||
homeLowCarbonCircumference !== undefined
|
||||
@ -535,7 +554,8 @@ class HuiEnergyDistrubutionCard
|
||||
>${formatConsumptionShort(
|
||||
this.hass,
|
||||
totalBatteryIn,
|
||||
"kWh"
|
||||
"kWh",
|
||||
targetEnergyUnit
|
||||
)}
|
||||
</span>
|
||||
<span class="battery-out">
|
||||
@ -546,7 +566,8 @@ class HuiEnergyDistrubutionCard
|
||||
>${formatConsumptionShort(
|
||||
this.hass,
|
||||
totalBatteryOut,
|
||||
"kWh"
|
||||
"kWh",
|
||||
targetEnergyUnit
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -239,7 +239,7 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
)}
|
||||
.path=${mdiImageFilterCenterFocus}
|
||||
style=${isDarkMode ? "color:#ffffff" : "color:#000000"}
|
||||
@click=${this._fitMap}
|
||||
@click=${this._resetFocus}
|
||||
tabindex="0"
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
@ -389,8 +389,8 @@ class HuiMapCard extends LitElement implements LovelaceCard {
|
||||
: (root.style.paddingBottom = "100%");
|
||||
}
|
||||
|
||||
private _fitMap() {
|
||||
this._map?.fitMap();
|
||||
private _resetFocus() {
|
||||
this._map?.fitMap({ unpause_autofit: true });
|
||||
}
|
||||
|
||||
private _toggleClusterMarkers() {
|
||||
|
@ -464,10 +464,11 @@ class HuiWeatherForecastCard extends LitElement implements LovelaceCard {
|
||||
if (this._config?.show_forecast !== false) {
|
||||
rows += 1;
|
||||
min_rows += 1;
|
||||
if (this._config?.forecast_type === "daily") {
|
||||
rows += 1;
|
||||
}
|
||||
}
|
||||
if (this._config?.forecast_type === "daily") {
|
||||
rows += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
columns: 12,
|
||||
rows: rows,
|
||||
|
@ -57,7 +57,7 @@ export class HuiWeatherForecastCardEditor
|
||||
|
||||
if (
|
||||
/* cannot show forecast in case it is unavailable on the entity */
|
||||
(config.show_forecast === true && this._hasForecast === false) ||
|
||||
(config.show_forecast !== false && this._hasForecast === false) ||
|
||||
/* cannot hide both weather and forecast, need one of them */
|
||||
(config.show_current === false && config.show_forecast === false)
|
||||
) {
|
||||
@ -65,6 +65,7 @@ export class HuiWeatherForecastCardEditor
|
||||
fireEvent(this, "config-changed", {
|
||||
config: { ...config, show_current: true, show_forecast: false },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!config.forecast_type ||
|
||||
|
@ -5093,6 +5093,7 @@
|
||||
"manufacturer": "Manufacturer",
|
||||
"model": "Model",
|
||||
"area": "Area",
|
||||
"floor": "Floor",
|
||||
"integration": "Integration",
|
||||
"battery": "Battery",
|
||||
"disabled_by": "Disabled",
|
||||
@ -5626,7 +5627,7 @@
|
||||
"other_networks": "Other networks",
|
||||
"my_network": "Preferred network",
|
||||
"no_preferred_network": "You don't have a preferred network yet.",
|
||||
"more_info": "More Info",
|
||||
"more_info": "More information",
|
||||
"add_open_thread_border_router": "Add an OpenThread border router",
|
||||
"reset_border_router": "Reset border router",
|
||||
"add_to_my_network": "Add to preferred network",
|
||||
@ -8465,7 +8466,7 @@
|
||||
"filter_states": "Filter states",
|
||||
"filter_attributes": "Filter attributes",
|
||||
"no_entities": "No entities",
|
||||
"more_info": "More Info",
|
||||
"more_info": "More info",
|
||||
"alert_entity_field": "Entity is a mandatory field",
|
||||
"last_updated": "[%key:ui::dialogs::more_info_control::last_updated%]",
|
||||
"last_changed": "[%key:ui::dialogs::more_info_control::last_changed%]",
|
||||
@ -8625,7 +8626,7 @@
|
||||
"input_select": "Input selects",
|
||||
"template": "Template entities",
|
||||
"universal": "Universal media player entities",
|
||||
"rest": "Rest entities and notify services",
|
||||
"rest": "REST entities and notify services",
|
||||
"command_line": "Command line entities",
|
||||
"filter": "Filter entities",
|
||||
"statistics": "Statistics entities",
|
||||
@ -9061,7 +9062,7 @@
|
||||
},
|
||||
"host_pid": {
|
||||
"title": "Host processes namespace",
|
||||
"description": "Usually, the processes the add-on runs, are isolated from all other system processes. The add-on author has requested the add-on to have access to the system processes running on the host system instance, and allow the add-on to spawn processes on the host system as well. This mode gives the add-on full access and control to your entire Home Assistant system, which adds security risks, and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on."
|
||||
"description": "Usually, the processes run by the add-on are isolated from all other system processes. The add-on author has requested the add-on to have access to the system processes running on the host system instance, and allow the add-on to spawn processes on the host system as well. This mode gives the add-on full access and control to your entire Home Assistant system, which adds security risks and could damage your system when misused. Therefore, this feature impacts the add-on security score negatively.\n\nThis level of access is not granted automatically and needs to be confirmed by you. To do this, you need to disable the protection mode on the add-on manually. Only disable the protection mode if you know, need AND trust the source of this add-on."
|
||||
},
|
||||
"apparmor": {
|
||||
"title": "AppArmor",
|
||||
|
48
test/common/url/search-params.test.ts
Normal file
48
test/common/url/search-params.test.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||
|
||||
import {
|
||||
addSearchParam,
|
||||
createSearchParam,
|
||||
extractSearchParam,
|
||||
extractSearchParamsObject,
|
||||
removeSearchParam,
|
||||
} from "../../../src/common/url/search-params";
|
||||
|
||||
const sortQueryString = (querystring: string): string =>
|
||||
querystring.split("&").sort().join("&");
|
||||
|
||||
vi.mock("../../../src/common/dom/get_main_window", () => ({
|
||||
mainWindow: { location: { search: "?param1=ab+c¶m2" } },
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe("Search Params Tests", () => {
|
||||
it("should extract all search params from window object", () => {
|
||||
expect(extractSearchParamsObject()).toEqual({ param1: "ab c", param2: "" });
|
||||
});
|
||||
|
||||
it("should return value for specified search param from window object", () => {
|
||||
expect(extractSearchParam("param1")).toEqual("ab c");
|
||||
});
|
||||
|
||||
it("should create query string from given object", () => {
|
||||
expect(
|
||||
sortQueryString(createSearchParam({ param1: "ab c", param2: "" }))
|
||||
).toEqual(sortQueryString("param1=ab+c¶m2="));
|
||||
});
|
||||
|
||||
it("should return query string which combines provided param object and window.location.search", () => {
|
||||
expect(
|
||||
sortQueryString(addSearchParam({ param4: "", param3: "x y" }))
|
||||
).toEqual(sortQueryString("param1=ab+c¶m2=¶m3=x+y¶m4="));
|
||||
});
|
||||
|
||||
it("should return query string from window.location.search but remove the provided param from it", () => {
|
||||
expect(sortQueryString(removeSearchParam("param2"))).toEqual(
|
||||
sortQueryString("param1=ab+c")
|
||||
);
|
||||
});
|
||||
});
|
@ -70,8 +70,10 @@ describe("Energy Short Format Test", () => {
|
||||
const hass = { locale: defaultLocale } as HomeAssistant;
|
||||
it("No Unit conversion", () => {
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "Wh"), "0 Wh");
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "kWh"), "0 kWh");
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "GWh"), "0 GWh");
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "kWh"), "0 Wh");
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "kWh", "kWh"), "0 kWh");
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "GWh"), "0 Wh");
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "GWh", "GWh"), "0 GWh");
|
||||
assert.strictEqual(formatConsumptionShort(hass, 0, "gal"), "0 gal");
|
||||
|
||||
assert.strictEqual(
|
||||
@ -139,6 +141,36 @@ describe("Energy Short Format Test", () => {
|
||||
"-1.23 Wh"
|
||||
);
|
||||
});
|
||||
it("Conversion with target unit", () => {
|
||||
assert.strictEqual(
|
||||
formatConsumptionShort(hass, 0.00012, "kWh", "Wh"),
|
||||
"0.12 Wh"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatConsumptionShort(hass, 0.00012, "kWh", "kWh"),
|
||||
"0 kWh"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatConsumptionShort(hass, 0.01012, "kWh", "kWh"),
|
||||
"0.01 kWh"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatConsumptionShort(hass, 0.00012, "kWh", "MWh"),
|
||||
"0 MWh"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatConsumptionShort(hass, 10.12345, "kWh", "kWh"),
|
||||
"10.1 kWh"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatConsumptionShort(hass, 10.12345, "kWh", "ZZZZZWh"),
|
||||
"10.1 kWh"
|
||||
);
|
||||
assert.strictEqual(
|
||||
formatConsumptionShort(hass, 151234.5678, "kWh", "MWh"),
|
||||
"151 MWh"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Energy Usage Calculation Tests", () => {
|
||||
|
136
yarn.lock
136
yarn.lock
@ -3818,22 +3818,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/client@npm:1.1.7":
|
||||
version: 1.1.7
|
||||
resolution: "@rsdoctor/client@npm:1.1.7"
|
||||
checksum: 10/4d17a357414f50b8ecb52e22530b6e657106c70b9c94d41f90f2ed2c833fddede80b3d23fb1aba47584719fa704391413124010d3a48ff065a8666dd07028ec8
|
||||
"@rsdoctor/client@npm:1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "@rsdoctor/client@npm:1.1.8"
|
||||
checksum: 10/fe815e1d6f96a75dcc44d3da25ba1dbf30e07a448809f2c17c91b8d107278b62488f302735cde148c9d64ab4dd9920f0d2fa62c7511a82a328c29db20b903fbd
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/core@npm:1.1.7":
|
||||
version: 1.1.7
|
||||
resolution: "@rsdoctor/core@npm:1.1.7"
|
||||
"@rsdoctor/core@npm:1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "@rsdoctor/core@npm:1.1.8"
|
||||
dependencies:
|
||||
"@rsbuild/plugin-check-syntax": "npm:1.3.0"
|
||||
"@rsdoctor/graph": "npm:1.1.7"
|
||||
"@rsdoctor/sdk": "npm:1.1.7"
|
||||
"@rsdoctor/types": "npm:1.1.7"
|
||||
"@rsdoctor/utils": "npm:1.1.7"
|
||||
"@rsdoctor/graph": "npm:1.1.8"
|
||||
"@rsdoctor/sdk": "npm:1.1.8"
|
||||
"@rsdoctor/types": "npm:1.1.8"
|
||||
"@rsdoctor/utils": "npm:1.1.8"
|
||||
axios: "npm:^1.10.0"
|
||||
browserslist-load-config: "npm:^1.0.0"
|
||||
enhanced-resolve: "npm:5.12.0"
|
||||
@ -3844,50 +3844,50 @@ __metadata:
|
||||
semver: "npm:^7.7.2"
|
||||
source-map: "npm:^0.7.4"
|
||||
webpack-bundle-analyzer: "npm:^4.10.2"
|
||||
checksum: 10/30a0adf465501cdaab1b8422529d21224935f61fb773a52075be4ba9d8ca684140bc1220e2ef1f77fbd75944645a564bd7eaba930dedb8c49f267fe0dcd99a73
|
||||
checksum: 10/1c71d9e2c25d8d7f52095c19be3c8784acb9ad9702103a5718d1c25bb20378af0490c3e95566f57c9aaf8e8c0c4b462dce75a5c1bbefb47021aff7365ae95b9c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/graph@npm:1.1.7":
|
||||
version: 1.1.7
|
||||
resolution: "@rsdoctor/graph@npm:1.1.7"
|
||||
"@rsdoctor/graph@npm:1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "@rsdoctor/graph@npm:1.1.8"
|
||||
dependencies:
|
||||
"@rsdoctor/types": "npm:1.1.7"
|
||||
"@rsdoctor/utils": "npm:1.1.7"
|
||||
"@rsdoctor/types": "npm:1.1.8"
|
||||
"@rsdoctor/utils": "npm:1.1.8"
|
||||
lodash.unionby: "npm:^4.8.0"
|
||||
socket.io: "npm:4.8.1"
|
||||
source-map: "npm:^0.7.4"
|
||||
checksum: 10/4314beb5119c7082df8b046c23fa27e4bcd80e3a504188e8a9e0d84e58713fae32e720b2cc4639f04ef53e891a323871e9378d37548769961a87934eff79825d
|
||||
checksum: 10/26553f153ec865b20aa1e112a57147c2282580579885595fda1b5269c00389fdead5f31ba4afdda4c5a229d7ae5ab3b70a5859bbcaddaeb835a232cb3d3dae8f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/rspack-plugin@npm:1.1.7":
|
||||
version: 1.1.7
|
||||
resolution: "@rsdoctor/rspack-plugin@npm:1.1.7"
|
||||
"@rsdoctor/rspack-plugin@npm:1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "@rsdoctor/rspack-plugin@npm:1.1.8"
|
||||
dependencies:
|
||||
"@rsdoctor/core": "npm:1.1.7"
|
||||
"@rsdoctor/graph": "npm:1.1.7"
|
||||
"@rsdoctor/sdk": "npm:1.1.7"
|
||||
"@rsdoctor/types": "npm:1.1.7"
|
||||
"@rsdoctor/utils": "npm:1.1.7"
|
||||
"@rsdoctor/core": "npm:1.1.8"
|
||||
"@rsdoctor/graph": "npm:1.1.8"
|
||||
"@rsdoctor/sdk": "npm:1.1.8"
|
||||
"@rsdoctor/types": "npm:1.1.8"
|
||||
"@rsdoctor/utils": "npm:1.1.8"
|
||||
lodash: "npm:^4.17.21"
|
||||
peerDependencies:
|
||||
"@rspack/core": "*"
|
||||
peerDependenciesMeta:
|
||||
"@rspack/core":
|
||||
optional: true
|
||||
checksum: 10/c2a4dfcf5bd18b59e1acadac62f8650847a634dfe469a5b15c217d94856f6a66170b743cc16af027f0c8096f05914423dac72fd2cfd453d95e1750918506d2b7
|
||||
checksum: 10/d01a41f19e812ba6eb90af57e8e376e70936fdac15c945d4e9787521a4acea03d808202a9afab6725df0c68a0521be8e7ffce50567a2be4d7a4d4c1bf1eecde0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/sdk@npm:1.1.7":
|
||||
version: 1.1.7
|
||||
resolution: "@rsdoctor/sdk@npm:1.1.7"
|
||||
"@rsdoctor/sdk@npm:1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "@rsdoctor/sdk@npm:1.1.8"
|
||||
dependencies:
|
||||
"@rsdoctor/client": "npm:1.1.7"
|
||||
"@rsdoctor/graph": "npm:1.1.7"
|
||||
"@rsdoctor/types": "npm:1.1.7"
|
||||
"@rsdoctor/utils": "npm:1.1.7"
|
||||
"@rsdoctor/client": "npm:1.1.8"
|
||||
"@rsdoctor/graph": "npm:1.1.8"
|
||||
"@rsdoctor/types": "npm:1.1.8"
|
||||
"@rsdoctor/utils": "npm:1.1.8"
|
||||
"@types/fs-extra": "npm:^11.0.4"
|
||||
body-parser: "npm:1.20.3"
|
||||
cors: "npm:2.8.5"
|
||||
@ -3895,18 +3895,18 @@ __metadata:
|
||||
fs-extra: "npm:^11.1.1"
|
||||
json-cycle: "npm:^1.5.0"
|
||||
lodash: "npm:^4.17.21"
|
||||
open: "npm:^10.1.2"
|
||||
open: "npm:^8.4.2"
|
||||
sirv: "npm:2.0.4"
|
||||
socket.io: "npm:4.8.1"
|
||||
source-map: "npm:^0.7.4"
|
||||
tapable: "npm:2.2.2"
|
||||
checksum: 10/e2b24eb7ac5aaf2872a4f4d3483be65ca4e9d17311a1fc1b4648c05cb697b232a2be1be3a3f5dc2afbcfb2d92f4373fa2b5d80d926a49bb1cb7238a8e1e5c41f
|
||||
checksum: 10/b2b732a6bef8116b422b35dfdbd2805b556a169177d93e86ac20274bf160de8dd9be7bc50ad6aca0adddbac147353309166c4e2ba48e7e4741ecbd71a8b823ef
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/types@npm:1.1.7":
|
||||
version: 1.1.7
|
||||
resolution: "@rsdoctor/types@npm:1.1.7"
|
||||
"@rsdoctor/types@npm:1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "@rsdoctor/types@npm:1.1.8"
|
||||
dependencies:
|
||||
"@types/connect": "npm:3.4.38"
|
||||
"@types/estree": "npm:1.0.5"
|
||||
@ -3920,16 +3920,16 @@ __metadata:
|
||||
optional: true
|
||||
webpack:
|
||||
optional: true
|
||||
checksum: 10/fd5aec14068746fb25dda4e93c6a804b01970dfe0c5d4a7f30dc6097923dc19b105fd5e899b279c066cc027666fa814c401bbdb1a3988a31dea8686830856050
|
||||
checksum: 10/abecd025399cafeddb563789c88d259129974aa5092993db32bcee5674987a99f05a047c0a3b728fd23c7d8f0a850e303c12ba7404e13a8f3dc2fd5214495096
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rsdoctor/utils@npm:1.1.7":
|
||||
version: 1.1.7
|
||||
resolution: "@rsdoctor/utils@npm:1.1.7"
|
||||
"@rsdoctor/utils@npm:1.1.8":
|
||||
version: 1.1.8
|
||||
resolution: "@rsdoctor/utils@npm:1.1.8"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:7.26.2"
|
||||
"@rsdoctor/types": "npm:1.1.7"
|
||||
"@rsdoctor/types": "npm:1.1.8"
|
||||
"@types/estree": "npm:1.0.5"
|
||||
acorn: "npm:^8.10.0"
|
||||
acorn-import-attributes: "npm:^1.9.5"
|
||||
@ -3945,7 +3945,7 @@ __metadata:
|
||||
picocolors: "npm:^1.1.1"
|
||||
rslog: "npm:^1.2.8"
|
||||
strip-ansi: "npm:^6.0.1"
|
||||
checksum: 10/70b1fbf149f79d574c889f39e01303898ced2215d6a964d0359c7286e0609f6b9a057a5b54913b76a27312ca0469995bd05d2074fbc82879c59331cef1143ee2
|
||||
checksum: 10/74fc27f2878d044da0056c02d34bd6a893471fde240130cec01c0017ba8a85799c08b51203ae4ad0cd2481ac58ac029d49472d58433e7c7bc09bc2b298a9920c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -6961,10 +6961,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"core-js@npm:3.43.0":
|
||||
version: 3.43.0
|
||||
resolution: "core-js@npm:3.43.0"
|
||||
checksum: 10/514952992863266b1a6a2d3c985e905461d37fe72d131d9320d5dbf01ac7e746f6fc53004b548347518cc832f7d2602b9a228acf6b5183e5cbede9dd296d73d3
|
||||
"core-js@npm:3.44.0":
|
||||
version: 3.44.0
|
||||
resolution: "core-js@npm:3.44.0"
|
||||
checksum: 10/759ef4ab0d12c9a6e8a32537d2b0fe64c2d7be40e13e0d7eb9604a970c380d64f37489dee03bd1c286c169e47a69d3ca2a968e8fcde0f78094ea22a20465d763
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -7258,6 +7258,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"define-lazy-prop@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "define-lazy-prop@npm:2.0.0"
|
||||
checksum: 10/0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"define-lazy-prop@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "define-lazy-prop@npm:3.0.0"
|
||||
@ -9367,7 +9374,7 @@ __metadata:
|
||||
"@octokit/plugin-retry": "npm:8.0.1"
|
||||
"@octokit/rest": "npm:22.0.0"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.1.7"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.1.8"
|
||||
"@rspack/cli": "npm:1.4.4"
|
||||
"@rspack/core": "npm:1.4.4"
|
||||
"@shoelace-style/shoelace": "npm:2.20.1"
|
||||
@ -9406,7 +9413,7 @@ __metadata:
|
||||
browserslist-useragent-regexp: "npm:4.1.3"
|
||||
color-name: "npm:2.0.0"
|
||||
comlink: "npm:4.4.2"
|
||||
core-js: "npm:3.43.0"
|
||||
core-js: "npm:3.44.0"
|
||||
cropperjs: "npm:1.6.2"
|
||||
date-fns: "npm:4.1.0"
|
||||
date-fns-tz: "npm:3.2.0"
|
||||
@ -9479,7 +9486,7 @@ __metadata:
|
||||
typescript: "npm:5.8.3"
|
||||
typescript-eslint: "npm:8.35.1"
|
||||
ua-parser-js: "npm:2.0.4"
|
||||
vis-data: "npm:7.1.9"
|
||||
vis-data: "npm:7.1.10"
|
||||
vite-tsconfig-paths: "npm:5.1.4"
|
||||
vitest: "npm:3.2.4"
|
||||
vue: "npm:2.7.16"
|
||||
@ -9992,7 +9999,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-docker@npm:^2.0.0":
|
||||
"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1":
|
||||
version: 2.2.1
|
||||
resolution: "is-docker@npm:2.2.1"
|
||||
bin:
|
||||
@ -11834,7 +11841,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"open@npm:^10.0.3, open@npm:^10.1.2":
|
||||
"open@npm:^10.0.3":
|
||||
version: 10.1.2
|
||||
resolution: "open@npm:10.1.2"
|
||||
dependencies:
|
||||
@ -11846,6 +11853,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"open@npm:^8.4.2":
|
||||
version: 8.4.2
|
||||
resolution: "open@npm:8.4.2"
|
||||
dependencies:
|
||||
define-lazy-prop: "npm:^2.0.0"
|
||||
is-docker: "npm:^2.1.1"
|
||||
is-wsl: "npm:^2.2.0"
|
||||
checksum: 10/acd81a1d19879c818acb3af2d2e8e9d81d17b5367561e623248133deb7dd3aefaed527531df2677d3e6aaf0199f84df57b6b2262babff8bf46ea0029aac536c9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"opener@npm:^1.5.2":
|
||||
version: 1.5.2
|
||||
resolution: "opener@npm:1.5.2"
|
||||
@ -14895,13 +14913,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"vis-data@npm:7.1.9":
|
||||
version: 7.1.9
|
||||
resolution: "vis-data@npm:7.1.9"
|
||||
"vis-data@npm:7.1.10":
|
||||
version: 7.1.10
|
||||
resolution: "vis-data@npm:7.1.10"
|
||||
peerDependencies:
|
||||
uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0
|
||||
uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
|
||||
vis-util: ^5.0.1
|
||||
checksum: 10/13cc6774cc225aa8a84d12c29d90188627fe8d937a93080113bd7b30d729fe8d6b2703322b41614ed48ad3f28f66e4090dfb1dce2ae7885a8938c2cffd7eebf3
|
||||
checksum: 10/23fb2ef26864153013372e1d95107765be86dd9ce96f987bf99fdd93759fbe5ec1bd2603d354ca18a03f0fb607b829396ec02fe005aead63ef24599512f21402
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user