197 files
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
|||||||
{"pid": 70, "version": 1, "ha_version": "2026.8.2", "start_ts": 1787246176.3005776}
|
{"pid": 72, "version": 1, "ha_version": "2026.8.2", "start_ts": 1787338100.2662094}
|
||||||
@@ -315,7 +315,7 @@
|
|||||||
"type": "module"
|
"type": "module"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "ba07e776ada54bb899ab0dfd6f6f2dbd",
|
"id": "3dc0faac727c4411b93fbf34ae873574",
|
||||||
"url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1",
|
"url": "/climate_scheduler/static/climate-scheduler-card.js?v=1.15.1",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,14 @@ class _OptionsFlowBase(TriggerConfigMixin, OptionsFlow):
|
|||||||
def _save_new_task(self) -> ConfigFlowResult:
|
def _save_new_task(self) -> ConfigFlowResult:
|
||||||
"""Save the current task and return to init."""
|
"""Save the current task and return to init."""
|
||||||
from .config_flow_schedule import build_new_task_record
|
from .config_flow_schedule import build_new_task_record
|
||||||
|
from .const import MAX_TASKS_PER_OBJECT
|
||||||
|
|
||||||
|
# Same bound the WS create chokepoint enforces (drift audit 2026-08:
|
||||||
|
# this options-flow copy had no cap, so the flow could inflate
|
||||||
|
# ConfigEntry.data past the limit the API refuses).
|
||||||
|
if len(self.config_entry.data.get(CONF_TASKS, {})) >= MAX_TASKS_PER_OBJECT:
|
||||||
|
self._current_task = {}
|
||||||
|
return self._show_init_menu()
|
||||||
|
|
||||||
task_id = uuid4().hex
|
task_id = uuid4().hex
|
||||||
task_data = build_new_task_record(
|
task_data = build_new_task_record(
|
||||||
|
|||||||
@@ -681,6 +681,9 @@ class TriggerConfigMixin:
|
|||||||
if to_state:
|
if to_state:
|
||||||
tc[CONF_TRIGGER_TO_STATE] = to_state
|
tc[CONF_TRIGGER_TO_STATE] = to_state
|
||||||
tc[CONF_TRIGGER_TARGET_CHANGES] = user_input.get(CONF_TRIGGER_TARGET_CHANGES, 1)
|
tc[CONF_TRIGGER_TARGET_CHANGES] = user_input.get(CONF_TRIGGER_TARGET_CHANGES, 1)
|
||||||
|
# #136: the new state must HOLD this long before a change counts
|
||||||
|
# (0 = count immediately — some sensors pulse only briefly).
|
||||||
|
tc[CONF_TRIGGER_FOR_MINUTES] = user_input.get(CONF_TRIGGER_FOR_MINUTES, 0)
|
||||||
_apply_recovery_flag(tc, user_input)
|
_apply_recovery_flag(tc, user_input)
|
||||||
_apply_combinator(tc, user_input)
|
_apply_combinator(tc, user_input)
|
||||||
|
|
||||||
@@ -711,6 +714,9 @@ class TriggerConfigMixin:
|
|||||||
mode=selector.NumberSelectorMode.BOX,
|
mode=selector.NumberSelectorMode.BOX,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
vol.Optional(CONF_TRIGGER_FOR_MINUTES, default=0): selector.NumberSelector(
|
||||||
|
selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX)
|
||||||
|
),
|
||||||
**_recovery_field(self._current_task.get("trigger_config")),
|
**_recovery_field(self._current_task.get("trigger_config")),
|
||||||
}
|
}
|
||||||
schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", [])))
|
schema_fields.update(_entity_logic_field(self._current_task.get("trigger_config", {}).get("entity_ids", [])))
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
import re
|
import re
|
||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||||
|
|
||||||
DOMAIN = "maintenance_supporter"
|
DOMAIN = "maintenance_supporter"
|
||||||
|
|
||||||
@@ -341,11 +341,18 @@ CONF_PARTS = "parts"
|
|||||||
# string literals in four files; find_fleet_entry, setup, the WS response
|
# string literals in four files; find_fleet_entry, setup, the WS response
|
||||||
# and the fleet exclusion all key on them).
|
# and the fleet exclusion all key on them).
|
||||||
BATTERY_FLEET_OBJECT_FLAG = "battery_fleet"
|
BATTERY_FLEET_OBJECT_FLAG = "battery_fleet"
|
||||||
|
# Marks the fleet's single aggregate task (detail view renders the battery
|
||||||
|
# section). Canonical home; helpers/battery_fleet_setup re-exports as TASK_FLAG.
|
||||||
|
BATTERY_FLEET_TASK_FLAG = "battery_fleet_task"
|
||||||
BATTERY_FLEET_EXCLUDED = "battery_fleet_excluded"
|
BATTERY_FLEET_EXCLUDED = "battery_fleet_excluded"
|
||||||
# #135: entity_ids manually ADDED to the fleet — batteries the discovery
|
# #135: entity_ids manually ADDED to the fleet — batteries the discovery
|
||||||
# heuristics miss (no device_class, odd naming, self-charging filter). An
|
# heuristics miss (no device_class, odd naming, self-charging filter). An
|
||||||
# include bypasses the heuristic and #107 filters, never the dedupe.
|
# include bypasses the heuristic and #107 filters, never the dedupe.
|
||||||
BATTERY_FLEET_INCLUDED = "battery_fleet_included"
|
BATTERY_FLEET_INCLUDED = "battery_fleet_included"
|
||||||
|
# #135 follow-up: fleet-wide opt-in that keeps self-charging devices (phones,
|
||||||
|
# vacuums, smart rings — the #107 filter) in the roster as rechargeables, so
|
||||||
|
# "needs a recharge" can notify like "needs a swap". Default off: #107 stays.
|
||||||
|
BATTERY_FLEET_TRACK_SELF_CHARGING = "battery_fleet_track_self_charging"
|
||||||
# Task-side link: task["consumes_parts"] = [{"part_id", "quantity"}] — a
|
# Task-side link: task["consumes_parts"] = [{"part_id", "quantity"}] — a
|
||||||
# completion decrements each linked part's stock.
|
# completion decrements each linked part's stock.
|
||||||
CONF_TASK_CONSUMES_PARTS = "consumes_parts"
|
CONF_TASK_CONSUMES_PARTS = "consumes_parts"
|
||||||
@@ -375,6 +382,7 @@ CONF_TASK_NFC_TAG = "nfc_tag_id"
|
|||||||
# v2.20 (#83): display unit for `reading`-type tasks ("kWh", "m³", ...).
|
# v2.20 (#83): display unit for `reading`-type tasks ("kWh", "m³", ...).
|
||||||
CONF_TASK_READING_UNIT = "reading_unit"
|
CONF_TASK_READING_UNIT = "reading_unit"
|
||||||
MAX_READING_UNIT_LENGTH = 32
|
MAX_READING_UNIT_LENGTH = 32
|
||||||
|
MAX_NFC_TAG_LENGTH = 256
|
||||||
CONF_TASK_INTERVAL_ANCHOR = "interval_anchor"
|
CONF_TASK_INTERVAL_ANCHOR = "interval_anchor"
|
||||||
CONF_TASK_SCHEDULE_TIME = "schedule_time"
|
CONF_TASK_SCHEDULE_TIME = "schedule_time"
|
||||||
CONF_TASK_PRIORITY = "priority"
|
CONF_TASK_PRIORITY = "priority"
|
||||||
@@ -704,6 +712,11 @@ MAX_GROUP_TASK_REFS = 200
|
|||||||
MAX_ID_LENGTH = 64 # entry_id, task_id, group_id (uuid hex = 32)
|
MAX_ID_LENGTH = 64 # entry_id, task_id, group_id (uuid hex = 32)
|
||||||
MAX_DATE_LENGTH = 20 # ISO 8601 date strings (e.g. 2026-04-21)
|
MAX_DATE_LENGTH = 20 # ISO 8601 date strings (e.g. 2026-04-21)
|
||||||
MAX_ENTITY_ID_LENGTH = 255 # HA entity_id max
|
MAX_ENTITY_ID_LENGTH = 255 # HA entity_id max
|
||||||
|
|
||||||
|
# "No usable reading" state pair — 13 call sites hand-typed the tuple before
|
||||||
|
# the 2026-08 drift audit. Built from HA's own constants so it can never
|
||||||
|
# drift from core.
|
||||||
|
UNAVAILABLE_STATES = (STATE_UNAVAILABLE, STATE_UNKNOWN)
|
||||||
MAX_ENTITY_SLUG_LENGTH = 64 # task entity_slug
|
MAX_ENTITY_SLUG_LENGTH = 64 # task entity_slug
|
||||||
MAX_INTERVAL_DAYS = 3650 # 10 years — caps date arithmetic overflow
|
MAX_INTERVAL_DAYS = 3650 # 10 years — caps date arithmetic overflow
|
||||||
# Import payload byte caps. CSV is flat (one row per task) so 1 MB is ample;
|
# Import payload byte caps. CSV is flat (one row per task) so 1 MB is ample;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ from .const import (
|
|||||||
SIGNAL_TASK_RESET,
|
SIGNAL_TASK_RESET,
|
||||||
STARTUP_GRACE_PERIOD_SECONDS,
|
STARTUP_GRACE_PERIOD_SECONDS,
|
||||||
TRIGGER_COMPLETION_COOLDOWN_SECONDS,
|
TRIGGER_COMPLETION_COOLDOWN_SECONDS,
|
||||||
|
UNAVAILABLE_STATES,
|
||||||
HistoryEntryType,
|
HistoryEntryType,
|
||||||
MaintenanceStatus,
|
MaintenanceStatus,
|
||||||
ScheduleType,
|
ScheduleType,
|
||||||
@@ -518,7 +519,7 @@ class MaintenanceCoordinator(DataUpdateCoordinator[dict[str, Any]]):
|
|||||||
issue_id = f"missing_trigger_{self.entry.entry_id}_{task_id}_{trigger_entity_id}"
|
issue_id = f"missing_trigger_{self.entry.entry_id}_{task_id}_{trigger_entity_id}"
|
||||||
state = self.hass.states.get(trigger_entity_id)
|
state = self.hass.states.get(trigger_entity_id)
|
||||||
|
|
||||||
if state is not None and state.state not in ("unavailable", "unknown"):
|
if state is not None and state.state not in UNAVAILABLE_STATES:
|
||||||
# Entity exists and is available
|
# Entity exists and is available
|
||||||
self._entity_missing_refresh_count.pop(entity_key, None)
|
self._entity_missing_refresh_count.pop(entity_key, None)
|
||||||
self._entity_unavailable_logged.pop(entity_key, None)
|
self._entity_unavailable_logged.pop(entity_key, None)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from .const import (
|
|||||||
CONF_TASKS,
|
CONF_TASKS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
GLOBAL_UNIQUE_ID,
|
GLOBAL_UNIQUE_ID,
|
||||||
|
UNAVAILABLE_STATES,
|
||||||
MaintenanceStatus,
|
MaintenanceStatus,
|
||||||
TriggerEntityState,
|
TriggerEntityState,
|
||||||
)
|
)
|
||||||
@@ -178,7 +179,7 @@ def _check_trigger_status(hass: HomeAssistant, data: Mapping[str, Any]) -> list[
|
|||||||
|
|
||||||
if state is None:
|
if state is None:
|
||||||
entity_health = TriggerEntityState.MISSING
|
entity_health = TriggerEntityState.MISSING
|
||||||
elif state.state in ("unavailable", "unknown"):
|
elif state.state in UNAVAILABLE_STATES:
|
||||||
entity_health = TriggerEntityState.UNAVAILABLE
|
entity_health = TriggerEntityState.UNAVAILABLE
|
||||||
else:
|
else:
|
||||||
entity_health = TriggerEntityState.AVAILABLE
|
entity_health = TriggerEntityState.AVAILABLE
|
||||||
|
|||||||
@@ -103,6 +103,11 @@ def _inject_per_entity_state(config: dict[str, Any], entity_state: dict[str, Any
|
|||||||
elif trigger_type == TriggerType.STATE_CHANGE:
|
elif trigger_type == TriggerType.STATE_CHANGE:
|
||||||
if "change_count" in entity_state:
|
if "change_count" in entity_state:
|
||||||
config["trigger_change_count"] = entity_state["change_count"]
|
config["trigger_change_count"] = entity_state["change_count"]
|
||||||
|
# #136: a hold window that was open when HA went down.
|
||||||
|
if "pending_since" in entity_state:
|
||||||
|
config["trigger_state_pending_since"] = entity_state["pending_since"]
|
||||||
|
if "pending_state" in entity_state:
|
||||||
|
config["trigger_state_pending_state"] = entity_state["pending_state"]
|
||||||
elif trigger_type == TriggerType.THRESHOLD:
|
elif trigger_type == TriggerType.THRESHOLD:
|
||||||
tes = entity_state.get("threshold_exceeded_since")
|
tes = entity_state.get("threshold_exceeded_since")
|
||||||
if tes:
|
if tes:
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
|
|||||||
from ...const import (
|
from ...const import (
|
||||||
EVENT_TRIGGER_ACTIVATED,
|
EVENT_TRIGGER_ACTIVATED,
|
||||||
EVENT_TRIGGER_DEACTIVATED,
|
EVENT_TRIGGER_DEACTIVATED,
|
||||||
|
UNAVAILABLE_STATES,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -80,7 +81,7 @@ class BaseTrigger(ABC):
|
|||||||
self._unsub_listener = async_track_state_change_event(self.hass, [self.entity_id], self._handle_state_change_event)
|
self._unsub_listener = async_track_state_change_event(self.hass, [self.entity_id], self._handle_state_change_event)
|
||||||
|
|
||||||
# If state is unknown/unavailable, schedule a retry
|
# If state is unknown/unavailable, schedule a retry
|
||||||
if state.state in ("unavailable", "unknown"):
|
if state.state in UNAVAILABLE_STATES:
|
||||||
_LOGGER.info(
|
_LOGGER.info(
|
||||||
"Trigger entity %s is '%s' — will retry evaluation in 30s",
|
"Trigger entity %s is '%s' — will retry evaluation in 30s",
|
||||||
self.entity_id,
|
self.entity_id,
|
||||||
@@ -115,7 +116,7 @@ class BaseTrigger(ABC):
|
|||||||
"""Re-check entity state after a delay."""
|
"""Re-check entity state after a delay."""
|
||||||
self._unsub_retry = None
|
self._unsub_retry = None
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
if state is None or state.state in ("unavailable", "unknown"):
|
if state is None or state.state in UNAVAILABLE_STATES:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Trigger entity %s still %s after retry",
|
"Trigger entity %s still %s after retry",
|
||||||
self.entity_id,
|
self.entity_id,
|
||||||
@@ -176,7 +177,7 @@ class BaseTrigger(ABC):
|
|||||||
# trigger entity is surfaced via the missing_trigger_entity repair
|
# trigger entity is surfaced via the missing_trigger_entity repair
|
||||||
# flow instead. (Numeric-only triggers: any non-numeric value below
|
# flow instead. (Numeric-only triggers: any non-numeric value below
|
||||||
# is likewise ignored via the _get_numeric_value None check.)
|
# is likewise ignored via the _get_numeric_value None check.)
|
||||||
if new_state.state in ("unavailable", "unknown"):
|
if new_state.state in UNAVAILABLE_STATES:
|
||||||
if not self._logged_unavailable:
|
if not self._logged_unavailable:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Trigger entity %s became %s — keeping last trigger state",
|
"Trigger entity %s became %s — keeping last trigger state",
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ from homeassistant.helpers.event import (
|
|||||||
async_track_time_interval,
|
async_track_time_interval,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ...const import UNAVAILABLE_STATES
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...sensor import MaintenanceSensor
|
from ...sensor import MaintenanceSensor
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
@@ -57,15 +59,15 @@ class RuntimeTrigger(BaseTrigger):
|
|||||||
self._target_hours: float = trigger_config.get("trigger_runtime_hours", 100.0)
|
self._target_hours: float = trigger_config.get("trigger_runtime_hours", 100.0)
|
||||||
self._accumulated_seconds: float = trigger_config.get("trigger_accumulated_seconds", 0.0)
|
self._accumulated_seconds: float = trigger_config.get("trigger_accumulated_seconds", 0.0)
|
||||||
|
|
||||||
# Restore on_since timestamp for restart recovery
|
# Restore on_since timestamp for restart recovery. parse_persisted_utc
|
||||||
|
# coerces a naive legacy payload to UTC — dt_util.parse_datetime kept
|
||||||
|
# it naive here, and `utcnow() - naive` raises TypeError in the
|
||||||
|
# elapsed math (drift audit 2026-08; the sibling triggers coerced).
|
||||||
|
from ...helpers.dates import parse_persisted_utc
|
||||||
|
|
||||||
on_since_str = trigger_config.get("trigger_on_since")
|
on_since_str = trigger_config.get("trigger_on_since")
|
||||||
self._on_since: str | None = None # ISO string stored for persistence
|
self._on_since_dt: datetime | None = parse_persisted_utc(on_since_str)
|
||||||
self._on_since_dt: datetime | None = None # parsed datetime for calculation
|
self._on_since: str | None = on_since_str if self._on_since_dt is not None else None
|
||||||
if on_since_str:
|
|
||||||
parsed = dt_util.parse_datetime(on_since_str)
|
|
||||||
if parsed:
|
|
||||||
self._on_since = on_since_str
|
|
||||||
self._on_since_dt = parsed
|
|
||||||
|
|
||||||
# Custom ON states (default: on, 1, true)
|
# Custom ON states (default: on, 1, true)
|
||||||
custom_on = trigger_config.get("trigger_on_states")
|
custom_on = trigger_config.get("trigger_on_states")
|
||||||
@@ -79,7 +81,7 @@ class RuntimeTrigger(BaseTrigger):
|
|||||||
async def async_setup(self) -> None:
|
async def async_setup(self) -> None:
|
||||||
"""Set up runtime trigger with state restoration."""
|
"""Set up runtime trigger with state restoration."""
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
if state is None or state.state in ("unavailable", "unknown"):
|
if state is None or state.state in UNAVAILABLE_STATES:
|
||||||
# No USABLE state yet (#131 family): "unavailable" must not read
|
# No USABLE state yet (#131 family): "unavailable" must not read
|
||||||
# as OFF — a running device whose sensor merely connects late
|
# as OFF — a running device whose sensor merely connects late
|
||||||
# would lose its restored on_since anchor and undercount. Keep
|
# would lose its restored on_since anchor and undercount. Keep
|
||||||
@@ -189,7 +191,7 @@ class RuntimeTrigger(BaseTrigger):
|
|||||||
old_val = self._tracked_value(old_state)
|
old_val = self._tracked_value(old_state)
|
||||||
|
|
||||||
# Handle unavailable/unknown — pause accumulation
|
# Handle unavailable/unknown — pause accumulation
|
||||||
if raw_state in ("unavailable", "unknown"):
|
if raw_state in UNAVAILABLE_STATES:
|
||||||
if self._on_since_dt is not None:
|
if self._on_since_dt is not None:
|
||||||
self._accumulate_elapsed()
|
self._accumulate_elapsed()
|
||||||
self._on_since_dt = None
|
self._on_since_dt = None
|
||||||
|
|||||||
@@ -3,13 +3,18 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from homeassistant.core import Event, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
EventStateChangedData,
|
EventStateChangedData,
|
||||||
|
async_call_later,
|
||||||
async_track_state_change_event,
|
async_track_state_change_event,
|
||||||
)
|
)
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
|
from ...const import UNAVAILABLE_STATES
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...sensor import MaintenanceSensor
|
from ...sensor import MaintenanceSensor
|
||||||
@@ -34,6 +39,12 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
# Setup saw no usable state -> reconcile on the first real one (#131).
|
# Setup saw no usable state -> reconcile on the first real one (#131).
|
||||||
# Class default so hand-built test instances inherit it.
|
# Class default so hand-built test instances inherit it.
|
||||||
_needs_latch_reconcile: bool = False
|
_needs_latch_reconcile: bool = False
|
||||||
|
# #136 hold-window state — class defaults for the same reason.
|
||||||
|
_for_minutes: int = 0
|
||||||
|
_pending_state: str | None = None
|
||||||
|
_pending_since: str | None = None
|
||||||
|
_interrupted_pending: str | None = None
|
||||||
|
_timer_cancel: CALLBACK_TYPE | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -58,6 +69,29 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
self._last_state: str | None = None
|
self._last_state: str | None = None
|
||||||
self._needs_latch_reconcile = False
|
self._needs_latch_reconcile = False
|
||||||
|
|
||||||
|
# #136: a transition only counts once the NEW state has HELD for this
|
||||||
|
# long. 0 (the default) counts immediately — deliberately, because some
|
||||||
|
# sensors express a real event only as a brief pulse; the filter is an
|
||||||
|
# opt-in for the flappy ones. Applies to BOTH modes: the single-shot
|
||||||
|
# alarm latch (target_changes == 1, the reporter's vacuum problem
|
||||||
|
# sensors glitching for seconds at night) and the cycle counter
|
||||||
|
# (a flicker is not a wash cycle).
|
||||||
|
self._for_minutes: int = int(trigger_config.get("trigger_for_minutes", 0) or 0)
|
||||||
|
self._timer_cancel: CALLBACK_TYPE | None = None
|
||||||
|
# A window cut short by an unavailability blip — the only case that
|
||||||
|
# may re-open on recovery (see _handle_state_transition).
|
||||||
|
self._interrupted_pending: str | None = None
|
||||||
|
# The state currently waiting out the hold window (None = no window).
|
||||||
|
self._pending_state: str | None = None
|
||||||
|
self._pending_since: str | None = None
|
||||||
|
# Persisted pending window from before a restart (consumed in setup).
|
||||||
|
from ...helpers.dates import parse_persisted_utc
|
||||||
|
|
||||||
|
self._restored_pending_state: str | None = trigger_config.get("trigger_state_pending_state")
|
||||||
|
self._restored_pending_dt: datetime | None = parse_persisted_utc(trigger_config.get("trigger_state_pending_since"))
|
||||||
|
if self._restored_pending_dt is None:
|
||||||
|
self._restored_pending_state = None
|
||||||
|
|
||||||
async def async_setup(self) -> None:
|
async def async_setup(self) -> None:
|
||||||
"""Set up state change trigger.
|
"""Set up state change trigger.
|
||||||
|
|
||||||
@@ -66,7 +100,7 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
appears (old_state=None), so the trigger will self-heal automatically.
|
appears (old_state=None), so the trigger will self-heal automatically.
|
||||||
"""
|
"""
|
||||||
state = self.hass.states.get(self.entity_id)
|
state = self.hass.states.get(self.entity_id)
|
||||||
if state is None or state.state in ("unavailable", "unknown"):
|
if state is None or state.state in UNAVAILABLE_STATES:
|
||||||
# No USABLE state yet — the #131 family: trigger setup races both
|
# No USABLE state yet — the #131 family: trigger setup races both
|
||||||
# the entity's registration AND its device readiness (a Zigbee /
|
# the entity's registration AND its device readiness (a Zigbee /
|
||||||
# Z-Wave problem sensor restores as unavailable long before it
|
# Z-Wave problem sensor restores as unavailable long before it
|
||||||
@@ -85,6 +119,7 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
|
|
||||||
self._last_state = state.state
|
self._last_state = state.state
|
||||||
self._reconcile_persisted_latch(state.state)
|
self._reconcile_persisted_latch(state.state)
|
||||||
|
self._resume_pending_window(state.state)
|
||||||
|
|
||||||
# Register state change listener (override base: we handle events differently)
|
# Register state change listener (override base: we handle events differently)
|
||||||
self._unsub_listener = async_track_state_change_event(self.hass, [self.entity_id], self._handle_state_transition)
|
self._unsub_listener = async_track_state_change_event(self.hass, [self.entity_id], self._handle_state_transition)
|
||||||
@@ -119,8 +154,7 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
self._change_count = 0
|
self._change_count = 0
|
||||||
self._current_value = 0.0
|
self._current_value = 0.0
|
||||||
self._triggered = False
|
self._triggered = False
|
||||||
if self.hass.is_running:
|
self._persist_runtime_soon()
|
||||||
self.hass.async_create_task(self._persist_change_count())
|
|
||||||
self.entity.async_update_trigger_state(
|
self.entity.async_update_trigger_state(
|
||||||
is_triggered=False,
|
is_triggered=False,
|
||||||
current_value=0.0,
|
current_value=0.0,
|
||||||
@@ -134,6 +168,113 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
trigger_entity_id=self.entity_id,
|
trigger_entity_id=self.entity_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _resume_pending_window(self, live_state: str) -> None:
|
||||||
|
"""Resume (or discard) a hold window persisted before a restart (#136).
|
||||||
|
|
||||||
|
Mirrors the threshold trigger's exceeded-since recovery: the wall-clock
|
||||||
|
anchor survives the restart, so a state that kept holding through the
|
||||||
|
downtime commits immediately once the window has fully elapsed, and
|
||||||
|
otherwise the timer resumes with the remaining duration. A live state
|
||||||
|
that no longer matches the anchored one discards the window.
|
||||||
|
"""
|
||||||
|
restored_state, restored_dt = self._restored_pending_state, self._restored_pending_dt
|
||||||
|
self._restored_pending_state = None
|
||||||
|
self._restored_pending_dt = None
|
||||||
|
if self._for_minutes <= 0 or restored_dt is None or restored_state is None or self._triggered:
|
||||||
|
return
|
||||||
|
if _norm_state(live_state) != _norm_state(restored_state):
|
||||||
|
self._persist_runtime_soon()
|
||||||
|
return
|
||||||
|
elapsed = (dt_util.utcnow() - restored_dt).total_seconds()
|
||||||
|
if elapsed >= self._for_minutes * 60:
|
||||||
|
_LOGGER.debug(
|
||||||
|
"State hold recovery: elapsed %.0fs >= %ds, committing immediately: %s",
|
||||||
|
elapsed,
|
||||||
|
self._for_minutes * 60,
|
||||||
|
self.entity_id,
|
||||||
|
)
|
||||||
|
self._commit_transition(live_state, None)
|
||||||
|
return
|
||||||
|
self._pending_state = restored_state
|
||||||
|
self._pending_since = restored_dt.isoformat()
|
||||||
|
remaining = max(self._for_minutes * 60 - elapsed, 0)
|
||||||
|
_LOGGER.debug("State hold recovery: %.0fs remaining: %s", remaining, self.entity_id)
|
||||||
|
self._start_hold_timer(remaining_seconds=remaining)
|
||||||
|
|
||||||
|
def _start_pending(self, new_val: str) -> None:
|
||||||
|
"""(Re)open the hold window for *new_val* — commits when the timer fires."""
|
||||||
|
self._cancel_timer()
|
||||||
|
self._pending_state = new_val
|
||||||
|
self._pending_since = dt_util.utcnow().isoformat()
|
||||||
|
self._persist_runtime_soon()
|
||||||
|
self._start_hold_timer()
|
||||||
|
|
||||||
|
def _start_hold_timer(self, remaining_seconds: float | None = None) -> None:
|
||||||
|
self._cancel_timer()
|
||||||
|
duration = remaining_seconds if remaining_seconds is not None else self._for_minutes * 60
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _timer_fired(_now: datetime) -> None:
|
||||||
|
pending = self._pending_state
|
||||||
|
self._pending_state = None
|
||||||
|
self._pending_since = None
|
||||||
|
self._timer_cancel = None
|
||||||
|
if pending is None:
|
||||||
|
return
|
||||||
|
# Safety net: only commit while the state still holds.
|
||||||
|
live = self.hass.states.get(self.entity_id)
|
||||||
|
if live is None or _norm_state(live.state) != _norm_state(pending):
|
||||||
|
self._persist_runtime_soon()
|
||||||
|
return
|
||||||
|
_LOGGER.debug(
|
||||||
|
"State hold timer fired: %s held %r for %d min",
|
||||||
|
self.entity_id,
|
||||||
|
pending,
|
||||||
|
self._for_minutes,
|
||||||
|
)
|
||||||
|
self._commit_transition(pending, None)
|
||||||
|
|
||||||
|
self._timer_cancel = async_call_later(self.hass, duration, _timer_fired)
|
||||||
|
|
||||||
|
def _clear_pending(self) -> None:
|
||||||
|
"""Abandon the hold window (state moved on before it elapsed)."""
|
||||||
|
if self._pending_state is None and self._timer_cancel is None:
|
||||||
|
return
|
||||||
|
self._cancel_timer()
|
||||||
|
self._pending_state = None
|
||||||
|
self._pending_since = None
|
||||||
|
self._persist_runtime_soon()
|
||||||
|
|
||||||
|
def _cancel_timer(self) -> None:
|
||||||
|
if self._timer_cancel is not None:
|
||||||
|
self._timer_cancel()
|
||||||
|
self._timer_cancel = None
|
||||||
|
|
||||||
|
def _commit_transition(self, new_val: str, old_val: str | None) -> None:
|
||||||
|
"""Count one matching transition (immediately, or after its hold)."""
|
||||||
|
self._pending_state = None
|
||||||
|
self._pending_since = None
|
||||||
|
self._change_count += 1
|
||||||
|
self._current_value = float(self._change_count)
|
||||||
|
self._persist_runtime_soon()
|
||||||
|
_LOGGER.debug(
|
||||||
|
"State change counted: %s (%s -> %s) count=%d/%d",
|
||||||
|
self.entity_id,
|
||||||
|
old_val if old_val is not None else "<held>",
|
||||||
|
new_val,
|
||||||
|
self._change_count,
|
||||||
|
self._target_changes,
|
||||||
|
)
|
||||||
|
|
||||||
|
was_triggered = self._triggered
|
||||||
|
is_triggered = self._change_count >= self._target_changes
|
||||||
|
self._triggered = is_triggered
|
||||||
|
|
||||||
|
if is_triggered and not was_triggered:
|
||||||
|
self._on_trigger_activated(float(self._change_count))
|
||||||
|
elif not is_triggered and was_triggered:
|
||||||
|
self._on_trigger_deactivated(float(self._change_count))
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _handle_state_transition(self, event: Event[EventStateChangedData]) -> None:
|
def _handle_state_transition(self, event: Event[EventStateChangedData]) -> None:
|
||||||
"""Handle state transition and count matching changes."""
|
"""Handle state transition and count matching changes."""
|
||||||
@@ -158,7 +299,7 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
# reconcile the persisted latch against it (issue #131): when the
|
# reconcile the persisted latch against it (issue #131): when the
|
||||||
# entity restores AFTER our setup, this appearance is the first
|
# entity restores AFTER our setup, this appearance is the first
|
||||||
# moment the latch can be checked against reality.
|
# moment the latch can be checked against reality.
|
||||||
if new_val not in ("unavailable", "unknown"):
|
if new_val not in UNAVAILABLE_STATES:
|
||||||
self._needs_latch_reconcile = False
|
self._needs_latch_reconcile = False
|
||||||
self._last_state = new_val
|
self._last_state = new_val
|
||||||
self._reconcile_persisted_latch(new_val)
|
self._reconcile_persisted_latch(new_val)
|
||||||
@@ -167,7 +308,14 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
old_val = old_state.state
|
old_val = old_state.state
|
||||||
|
|
||||||
# Handle unavailable/unknown with log-once pattern
|
# Handle unavailable/unknown with log-once pattern
|
||||||
if new_val in ("unavailable", "unknown"):
|
if new_val in UNAVAILABLE_STATES:
|
||||||
|
# #136: an unavailability blip is not "the state held" — abandon
|
||||||
|
# the hold window, but REMEMBER it: only a window that was
|
||||||
|
# actually running may re-open when the entity comes back (else a
|
||||||
|
# blip on a long-settled state would count a phantom transition).
|
||||||
|
if self._pending_state is not None:
|
||||||
|
self._interrupted_pending = self._pending_state
|
||||||
|
self._clear_pending()
|
||||||
if not self._logged_unavailable:
|
if not self._logged_unavailable:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Trigger entity %s became %s",
|
"Trigger entity %s became %s",
|
||||||
@@ -185,6 +333,19 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
new_val,
|
new_val,
|
||||||
)
|
)
|
||||||
self._logged_unavailable = False
|
self._logged_unavailable = False
|
||||||
|
# #136: a window was running when the blip hit and the state came
|
||||||
|
# back unchanged — restart it (fresh clock; the normal transition
|
||||||
|
# path below cannot, because effective_old equals new_val here).
|
||||||
|
interrupted = self._interrupted_pending
|
||||||
|
self._interrupted_pending = None
|
||||||
|
if (
|
||||||
|
self._for_minutes > 0
|
||||||
|
and not self._triggered
|
||||||
|
and self._pending_state is None
|
||||||
|
and interrupted is not None
|
||||||
|
and _norm_state(new_val) == _norm_state(interrupted)
|
||||||
|
):
|
||||||
|
self._start_pending(new_val)
|
||||||
|
|
||||||
# First REAL state after a setup that saw none/unavailable (#131
|
# First REAL state after a setup that saw none/unavailable (#131
|
||||||
# family): reconcile the persisted latch against it instead of
|
# family): reconcile the persisted latch against it instead of
|
||||||
@@ -199,9 +360,16 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
|
|
||||||
# Use _last_state as fallback when old_val is unavailable/unknown
|
# Use _last_state as fallback when old_val is unavailable/unknown
|
||||||
effective_old = old_val
|
effective_old = old_val
|
||||||
if old_val in ("unavailable", "unknown") and self._last_state is not None:
|
if old_val in UNAVAILABLE_STATES and self._last_state is not None:
|
||||||
effective_old = self._last_state
|
effective_old = self._last_state
|
||||||
|
|
||||||
|
# #136: any real state movement means the previous state did NOT hold
|
||||||
|
# — abandon a running hold window (a matching transition right below
|
||||||
|
# opens a fresh one) and invalidate a blip-interruption marker.
|
||||||
|
if effective_old != new_val:
|
||||||
|
self._interrupted_pending = None
|
||||||
|
self._clear_pending()
|
||||||
|
|
||||||
# Check if transition matches pattern
|
# Check if transition matches pattern
|
||||||
matches = True
|
matches = True
|
||||||
if self._from_state is not None and _norm_state(effective_old) != self._from_state:
|
if self._from_state is not None and _norm_state(effective_old) != self._from_state:
|
||||||
@@ -210,28 +378,11 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
matches = False
|
matches = False
|
||||||
|
|
||||||
if matches and effective_old != new_val:
|
if matches and effective_old != new_val:
|
||||||
self._change_count += 1
|
if self._for_minutes > 0:
|
||||||
self._current_value = float(self._change_count)
|
# #136: the transition only counts once new_val has held.
|
||||||
# Persist change count to survive restarts
|
self._start_pending(new_val)
|
||||||
if self.hass.is_running:
|
else:
|
||||||
self.hass.async_create_task(self._persist_change_count())
|
self._commit_transition(new_val, old_val)
|
||||||
_LOGGER.debug(
|
|
||||||
"State change counted: %s (%s -> %s) count=%d/%d",
|
|
||||||
self.entity_id,
|
|
||||||
old_val,
|
|
||||||
new_val,
|
|
||||||
self._change_count,
|
|
||||||
self._target_changes,
|
|
||||||
)
|
|
||||||
|
|
||||||
was_triggered = self._triggered
|
|
||||||
is_triggered = self._change_count >= self._target_changes
|
|
||||||
self._triggered = is_triggered
|
|
||||||
|
|
||||||
if is_triggered and not was_triggered:
|
|
||||||
self._on_trigger_activated(float(self._change_count))
|
|
||||||
elif not is_triggered and was_triggered:
|
|
||||||
self._on_trigger_deactivated(float(self._change_count))
|
|
||||||
|
|
||||||
# Latch recovery: a single-shot state alarm (target_changes == 1 — an
|
# Latch recovery: a single-shot state alarm (target_changes == 1 — an
|
||||||
# adopted problem sensor or an appliance event) clears when the entity
|
# adopted problem sensor or an appliance event) clears when the entity
|
||||||
@@ -247,8 +398,7 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
):
|
):
|
||||||
self._change_count = 0
|
self._change_count = 0
|
||||||
self._current_value = 0.0
|
self._current_value = 0.0
|
||||||
if self.hass.is_running:
|
self._persist_runtime_soon()
|
||||||
self.hass.async_create_task(self._persist_change_count())
|
|
||||||
self._triggered = False
|
self._triggered = False
|
||||||
self._on_trigger_deactivated(0.0)
|
self._on_trigger_deactivated(0.0)
|
||||||
|
|
||||||
@@ -268,19 +418,39 @@ class StateChangeTrigger(BaseTrigger):
|
|||||||
"""Reset the change counter (after maintenance)."""
|
"""Reset the change counter (after maintenance)."""
|
||||||
self._change_count = 0
|
self._change_count = 0
|
||||||
self._current_value = 0.0
|
self._current_value = 0.0
|
||||||
if self.hass.is_running:
|
self._persist_runtime_soon()
|
||||||
self.hass.async_create_task(self._persist_change_count())
|
|
||||||
_LOGGER.debug("State change counter reset: %s", self.entity_id)
|
_LOGGER.debug("State change counter reset: %s", self.entity_id)
|
||||||
|
|
||||||
async def _persist_change_count(self) -> None:
|
def _persist_runtime_soon(self) -> None:
|
||||||
"""Persist change count to the Store for survival across restarts."""
|
if self.hass.is_running:
|
||||||
|
self.hass.async_create_task(self._persist_runtime())
|
||||||
|
|
||||||
|
async def _persist_runtime(self) -> None:
|
||||||
|
"""Persist the full runtime dict (count + hold window) to the Store.
|
||||||
|
|
||||||
|
Always the COMPLETE dict: set_trigger_runtime replaces per-entity
|
||||||
|
state wholesale, so a partial write would drop the other half.
|
||||||
|
"""
|
||||||
|
data: dict[str, Any] = {"change_count": self._change_count}
|
||||||
|
if self._pending_since is not None and self._pending_state is not None:
|
||||||
|
data["pending_since"] = self._pending_since
|
||||||
|
data["pending_state"] = self._pending_state
|
||||||
await self._coordinator.async_persist_trigger_runtime(
|
await self._coordinator.async_persist_trigger_runtime(
|
||||||
self._task_id,
|
self._task_id,
|
||||||
{"change_count": self._change_count},
|
data,
|
||||||
entity_id=self.entity_id,
|
entity_id=self.entity_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_teardown(self) -> None:
|
||||||
|
"""Clean up the hold timer on teardown."""
|
||||||
|
self._cancel_timer()
|
||||||
|
await super().async_teardown()
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Reset trigger and counter."""
|
"""Reset trigger, counter and any running hold window."""
|
||||||
super().reset()
|
super().reset()
|
||||||
|
self._cancel_timer()
|
||||||
|
self._pending_state = None
|
||||||
|
self._pending_since = None
|
||||||
|
self._interrupted_pending = None
|
||||||
self.reset_count()
|
self.reset_count()
|
||||||
|
|||||||
@@ -48,22 +48,11 @@ class ThresholdTrigger(BaseTrigger):
|
|||||||
self._timer_cancel: CALLBACK_TYPE | None = None
|
self._timer_cancel: CALLBACK_TYPE | None = None
|
||||||
|
|
||||||
# Restore persisted exceeded-since timestamp (survives HA restarts)
|
# Restore persisted exceeded-since timestamp (survives HA restarts)
|
||||||
exceeded_since = trigger_config.get("trigger_threshold_exceeded_since")
|
from ...helpers.dates import parse_persisted_utc
|
||||||
self._exceeded_since: str | None = None
|
|
||||||
self._exceeded_since_dt: datetime | None = None
|
|
||||||
if exceeded_since:
|
|
||||||
try:
|
|
||||||
parsed = datetime.fromisoformat(exceeded_since)
|
|
||||||
# Older payloads may be naive — assume UTC since live writes
|
|
||||||
# use dt_util.utcnow().isoformat() (TZ-aware).
|
|
||||||
if parsed.tzinfo is None:
|
|
||||||
from datetime import UTC
|
|
||||||
|
|
||||||
parsed = parsed.replace(tzinfo=UTC)
|
exceeded_since = trigger_config.get("trigger_threshold_exceeded_since")
|
||||||
self._exceeded_since_dt = parsed
|
self._exceeded_since_dt: datetime | None = parse_persisted_utc(exceeded_since)
|
||||||
self._exceeded_since = exceeded_since
|
self._exceeded_since: str | None = exceeded_since if self._exceeded_since_dt is not None else None
|
||||||
except (ValueError, TypeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _value_exceeds_threshold(self, value: float) -> bool:
|
def _value_exceeds_threshold(self, value: float) -> bool:
|
||||||
"""Check if the value exceeds configured thresholds."""
|
"""Check if the value exceeds configured thresholds."""
|
||||||
|
|||||||
+32
@@ -62,3 +62,35 @@ describe("adopt-problem-sensors dialog: suggested part", () => {
|
|||||||
expect(byId["binary_sensor.pump_problem"].part_id).to.equal(undefined);
|
expect(byId["binary_sensor.pump_problem"].part_id).to.equal(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("adopt-problem-sensors dialog: flicker filter (#136)", () => {
|
||||||
|
it("defaults to 0 and then omits for_minutes from the payload", async () => {
|
||||||
|
const { el, sent } = await mountOpen();
|
||||||
|
const input = el.shadowRoot!.querySelector<HTMLInputElement>(".for-input")!;
|
||||||
|
expect(input, "the for-minutes field renders").to.exist;
|
||||||
|
expect(input.value).to.equal("0");
|
||||||
|
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click();
|
||||||
|
await el.updateComplete;
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
|
||||||
|
selections: Array<{ for_minutes?: number }>;
|
||||||
|
};
|
||||||
|
expect(adopt.selections.every((s) => s.for_minutes === undefined)).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("forwards the entered minutes on every selection", async () => {
|
||||||
|
const { el, sent } = await mountOpen();
|
||||||
|
const input = el.shadowRoot!.querySelector<HTMLInputElement>(".for-input")!;
|
||||||
|
input.value = "10";
|
||||||
|
input.dispatchEvent(new Event("input"));
|
||||||
|
await el.updateComplete;
|
||||||
|
el.shadowRoot!.querySelectorAll<HTMLElement>("ha-button")[1].click();
|
||||||
|
await el.updateComplete;
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
const adopt = sent.find((m) => m.type === "maintenance_supporter/problem_sensors/adopt")! as {
|
||||||
|
selections: Array<{ for_minutes?: number }>;
|
||||||
|
};
|
||||||
|
expect(adopt.selections.length).to.be.greaterThan(0);
|
||||||
|
expect(adopt.selections.every((s) => s.for_minutes === 10)).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
* the no-fetch fast paths of ensureLocale/isLocaleLoaded.
|
* the no-fetch fast paths of ensureLocale/isLocaleLoaded.
|
||||||
*/
|
*/
|
||||||
import { expect } from "@open-wc/testing";
|
import { expect } from "@open-wc/testing";
|
||||||
import { t, isLocaleLoaded, ensureLocale } from "../styles";
|
import { t, isLocaleLoaded, ensureLocale, seedEnglish } from "../styles";
|
||||||
|
|
||||||
describe("i18n runtime loader", () => {
|
describe("i18n runtime loader", () => {
|
||||||
it("serves bundled English synchronously", () => {
|
it("serves bundled English synchronously", () => {
|
||||||
@@ -60,4 +60,25 @@ describe("i18n runtime loader", () => {
|
|||||||
delete g!.store.pt;
|
delete g!.store.pt;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("seedEnglish MERGES so a stale first-loader cannot shadow newer keys (#135 regression)", () => {
|
||||||
|
// After an update, a cached app shell can load an OLD card bundle first;
|
||||||
|
// its EN table lacks every key the fresh panel introduced. First-wins
|
||||||
|
// seeding froze that stale table and the panel rendered raw keys
|
||||||
|
// ("BATTERY_FLEET_ADD"). The merge keeps existing entries (steady state
|
||||||
|
// unchanged) but lets every bundle contribute the keys it knows.
|
||||||
|
const g = (window as unknown as {
|
||||||
|
__msLocales: { store: Record<string, Record<string, string>> };
|
||||||
|
}).__msLocales;
|
||||||
|
const original = g.store.en;
|
||||||
|
try {
|
||||||
|
// Simulate the stale bundle having seeded first with a tiny old table.
|
||||||
|
g.store.en = { loading: "Old loading text" };
|
||||||
|
seedEnglish({ loading: "Fresh loading text", brand_new_key: "New feature label" });
|
||||||
|
expect(t("loading", "en"), "existing entries win conflicts").to.equal("Old loading text");
|
||||||
|
expect(t("brand_new_key", "en"), "newer keys are contributed, never raw").to.equal("New feature label");
|
||||||
|
} finally {
|
||||||
|
g.store.en = original;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+17
@@ -162,6 +162,23 @@ describe("task-dialog trigger_config roundtrip closure (#103 class)", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("state_change: the hold filter (for_minutes, #136) survives the roundtrip", async () => {
|
||||||
|
const tc = await saveRoundtrip({
|
||||||
|
type: "state_change",
|
||||||
|
entity_id: "binary_sensor.vacuum_problem",
|
||||||
|
entity_ids: ["binary_sensor.vacuum_problem"],
|
||||||
|
trigger_to_state: "on",
|
||||||
|
trigger_target_changes: 1,
|
||||||
|
trigger_for_minutes: 10,
|
||||||
|
auto_complete_on_recovery: true,
|
||||||
|
});
|
||||||
|
expect(tc).to.deep.include({
|
||||||
|
type: "state_change",
|
||||||
|
trigger_to_state: "on",
|
||||||
|
trigger_for_minutes: 10,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("compound: per-condition attribute/baseline/entity_logic survive via carry", async () => {
|
it("compound: per-condition attribute/baseline/entity_logic survive via carry", async () => {
|
||||||
const tc = await saveRoundtrip({
|
const tc = await saveRoundtrip({
|
||||||
type: "compound",
|
type: "compound",
|
||||||
|
|||||||
@@ -122,6 +122,22 @@ describe("task-documents", () => {
|
|||||||
window.open = orig;
|
window.open = orig;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("refuses to window.open a non-http(s) weblink (defense-in-depth, XSS)", async () => {
|
||||||
|
const EVIL = { id: "d4", kind: "weblink", title: "Evil", url: "javascript:alert(document.cookie)", tags: [], task_ids: ["t1"] };
|
||||||
|
const orig = window.open;
|
||||||
|
let opened = "NOT_CALLED";
|
||||||
|
window.open = ((u: string) => { opened = u; return null; }) as typeof window.open;
|
||||||
|
try {
|
||||||
|
const { el } = await mount(true, [EVIL]);
|
||||||
|
const info = el.shadowRoot!.querySelector<HTMLElement>(".tdoc-row .tdoc-info")!;
|
||||||
|
info.click();
|
||||||
|
await el.updateComplete;
|
||||||
|
expect(opened, "a javascript: weblink must never reach window.open").to.equal("NOT_CALLED");
|
||||||
|
} finally {
|
||||||
|
window.open = orig;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── part mode (v2.26): same component, linking via part_ids ─────────────────
|
// ── part mode (v2.26): same component, linking via part_ids ─────────────────
|
||||||
|
|||||||
+35
@@ -50,6 +50,9 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
|
|||||||
@state() private _selected: Set<string> = new Set();
|
@state() private _selected: Set<string> = new Set();
|
||||||
@state() private _users: HAUser[] = [];
|
@state() private _users: HAUser[] = [];
|
||||||
@state() private _responsible = "";
|
@state() private _responsible = "";
|
||||||
|
// #136: minutes the problem must persist before the created task triggers
|
||||||
|
// (one field applied to every selection). "0" = react to the first flicker.
|
||||||
|
@state() private _forMinutes = "0";
|
||||||
|
|
||||||
private _localeReady = false;
|
private _localeReady = false;
|
||||||
private _userService: UserService | null = null;
|
private _userService: UserService | null = null;
|
||||||
@@ -72,6 +75,7 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
|
|||||||
this._sensors = [];
|
this._sensors = [];
|
||||||
this._selected = new Set();
|
this._selected = new Set();
|
||||||
this._responsible = "";
|
this._responsible = "";
|
||||||
|
this._forMinutes = "0";
|
||||||
try {
|
try {
|
||||||
if (!this._userService) this._userService = new UserService(this.hass);
|
if (!this._userService) this._userService = new UserService(this.hass);
|
||||||
else this._userService.updateHass(this.hass);
|
else this._userService.updateHass(this.hass);
|
||||||
@@ -126,6 +130,7 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
|
|||||||
device_id: s.device_id ?? undefined,
|
device_id: s.device_id ?? undefined,
|
||||||
part_id: s.suggested_part_id ?? undefined,
|
part_id: s.suggested_part_id ?? undefined,
|
||||||
responsible_user_id: this._responsible || undefined,
|
responsible_user_id: this._responsible || undefined,
|
||||||
|
for_minutes: parseInt(this._forMinutes, 10) > 0 ? parseInt(this._forMinutes, 10) : undefined,
|
||||||
}));
|
}));
|
||||||
const result = await this.hass.connection.sendMessagePromise<AdoptResponse>({
|
const result = await this.hass.connection.sendMessagePromise<AdoptResponse>({
|
||||||
type: "maintenance_supporter/problem_sensors/adopt",
|
type: "maintenance_supporter/problem_sensors/adopt",
|
||||||
@@ -214,6 +219,23 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
|
||||||
|
${!this._loading && this._sensors.length > 0
|
||||||
|
? html`
|
||||||
|
<label class="responsible">
|
||||||
|
<span>${t("for_at_least_minutes", L)}</span>
|
||||||
|
<input
|
||||||
|
class="for-input"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1440"
|
||||||
|
.value=${this._forMinutes}
|
||||||
|
@input=${(e: Event) => (this._forMinutes = (e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div class="for-hint">${t("adopt_for_minutes_hint", L)}</div>
|
||||||
|
`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
${!this._loading && this._sensors.length > 0 && this._users.length > 0
|
${!this._loading && this._sensors.length > 0 && this._users.length > 0
|
||||||
? html`
|
? html`
|
||||||
<label class="responsible">
|
<label class="responsible">
|
||||||
@@ -382,6 +404,19 @@ export class MaintenanceAdoptProblemSensorsDialog extends LitElement {
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.for-input {
|
||||||
|
width: 70px;
|
||||||
|
background: var(--card-background-color);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
.for-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
margin: -4px 0 2px;
|
||||||
|
}
|
||||||
.responsible select {
|
.responsible select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
|
|||||||
+145
-8
@@ -66,11 +66,17 @@ interface Overview {
|
|||||||
needs_soon: Record<string, number>;
|
needs_soon: Record<string, number>;
|
||||||
types: string[];
|
types: string[];
|
||||||
excluded?: { entity_id: string; device_name: string }[];
|
excluded?: { entity_id: string; device_name: string }[];
|
||||||
|
// #135 follow-up: fleet-wide "track self-charging devices" opt-in.
|
||||||
|
track_self_charging?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MaintenanceBatteryFleetSection extends LitElement {
|
export class MaintenanceBatteryFleetSection extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
|
/** Flat mode drops the section's own card chrome (border/background) —
|
||||||
|
* set by the Lovelace card wrapper, whose ha-card provides the chrome. */
|
||||||
|
@property({ type: Boolean }) public flat = false;
|
||||||
|
|
||||||
@state() private _ov: Overview | null = null;
|
@state() private _ov: Overview | null = null;
|
||||||
@state() private _loading = false;
|
@state() private _loading = false;
|
||||||
@state() private _marking = false;
|
@state() private _marking = false;
|
||||||
@@ -198,6 +204,26 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #135 follow-up: fleet-wide opt-in that keeps self-charging devices
|
||||||
|
// (phones, vacuums, smart rings) in the roster as rechargeables.
|
||||||
|
private async _setTrackSelf(e: Event): Promise<void> {
|
||||||
|
const enabled = (e.target as HTMLInputElement).checked;
|
||||||
|
if (this._marking) return;
|
||||||
|
this._marking = true;
|
||||||
|
this._error = "";
|
||||||
|
try {
|
||||||
|
await this.hass.connection.sendMessagePromise({
|
||||||
|
type: "maintenance_supporter/battery_fleet/set_track_self_charging",
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
|
await this._load();
|
||||||
|
} catch (e2) {
|
||||||
|
this._error = describeWsError(e2, this._lang);
|
||||||
|
} finally {
|
||||||
|
this._marking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Lazy: the recorder-backed history is fetched once, when the roster is
|
/** Lazy: the recorder-backed history is fetched once, when the roster is
|
||||||
* first expanded — most panel visits never open it. */
|
* first expanded — most panel visits never open it. */
|
||||||
private _loadHistory = async (e: Event): Promise<void> => {
|
private _loadHistory = async (e: Event): Promise<void> => {
|
||||||
@@ -517,6 +543,16 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
|||||||
></ha-selector>
|
></ha-selector>
|
||||||
<div class="bf-roster-hint">${t("battery_fleet_add_hint", L)}</div>
|
<div class="bf-roster-hint">${t("battery_fleet_add_hint", L)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<label class="bf-track-self">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
.checked=${!!ov.track_self_charging}
|
||||||
|
.disabled=${this._marking}
|
||||||
|
@change=${this._setTrackSelf}
|
||||||
|
/>
|
||||||
|
${t("battery_fleet_track_self", L)}
|
||||||
|
</label>
|
||||||
|
<div class="bf-roster-hint">${t("battery_fleet_track_self_hint", L)}</div>
|
||||||
</details>
|
</details>
|
||||||
`
|
`
|
||||||
: nothing}
|
: nothing}
|
||||||
@@ -558,6 +594,13 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
:host([flat]) .bf-card {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
.bf-head {
|
.bf-head {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -614,22 +657,69 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
|||||||
.bf-list {
|
.bf-list {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
/* Cross-row column alignment (same subgrid pattern as the task table,
|
||||||
|
* issue 66): the LIST owns one column template, every row spans it via
|
||||||
|
* subgrid, and each element is PINNED to its column below - so an
|
||||||
|
* optional element (sparkline, percentage, forecast date) leaves its
|
||||||
|
* column empty instead of letting the rest of the row drift. max-content
|
||||||
|
* columns collapse to 0 when a whole list never fills them (the low list
|
||||||
|
* has no status chip, no sparkline, no date). */
|
||||||
.bf-rows {
|
.bf-rows {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: minmax(0, 1fr) repeat(9, max-content);
|
||||||
gap: 4px;
|
column-gap: 8px;
|
||||||
}
|
}
|
||||||
.bf-row {
|
.bf-row {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: subgrid;
|
||||||
|
grid-column: 1 / -1;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
border-bottom: 1px solid var(--divider-color);
|
border-bottom: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
/* Column pinning: 1 name, 2 status/offline chip, 3 type, 4 charging
|
||||||
|
* icon, 5 sparkline, 6 level bar, 7 percentage, 8 row action
|
||||||
|
* (mark-one / record-swap), 9 forecast date, 10 exclude eye. */
|
||||||
.bf-dev {
|
.bf-dev {
|
||||||
flex: 1;
|
grid-column: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.bf-offline,
|
||||||
|
.bf-status {
|
||||||
|
grid-column: 2;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
.bf-type {
|
||||||
|
grid-column: 3;
|
||||||
|
}
|
||||||
|
.bf-recharge {
|
||||||
|
grid-column: 4;
|
||||||
|
}
|
||||||
|
.bf-spark {
|
||||||
|
grid-column: 5;
|
||||||
|
}
|
||||||
|
.bf-bar {
|
||||||
|
grid-column: 6;
|
||||||
|
}
|
||||||
|
.bf-level {
|
||||||
|
grid-column: 7;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
.bf-row .bf-mark {
|
||||||
|
grid-column: 8;
|
||||||
|
}
|
||||||
|
.bf-predicted {
|
||||||
|
grid-column: 9;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
.bf-row .bf-mark.bf-exclude {
|
||||||
|
grid-column: 10;
|
||||||
|
}
|
||||||
|
.bf-offline {
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
font-size: 12px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
.bf-type {
|
.bf-type {
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -648,13 +738,48 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
/* On phones the row cannot fit name + chips + curve + bar + date: the
|
/* On phones the row cannot fit name + chips + curve + bar + date in ONE
|
||||||
* decorations yield (the percentage still carries the number). */
|
* line: the decorations yield (the percentage still carries the number)
|
||||||
|
* and the row wraps to two lines - the name spans the full width, the
|
||||||
|
* status chip moves under it (left, into the name column) and the rest
|
||||||
|
* keeps its pinned subgrid column, so type / percentage / date / eye
|
||||||
|
* stay aligned across rows. Without this the fixed max-content columns
|
||||||
|
* overflowed 400 px and the chips overlapped the wrapped names. */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.bf-spark,
|
.bf-spark,
|
||||||
.bf-bar {
|
.bf-bar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.bf-row {
|
||||||
|
row-gap: 2px;
|
||||||
|
}
|
||||||
|
.bf-dev {
|
||||||
|
grid-column: 1 / 9;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
.bf-offline,
|
||||||
|
.bf-status {
|
||||||
|
/* Line 1, RIGHT - over the date+eye columns, which are wide enough
|
||||||
|
* for any chip. Pinning the chip into the 1fr rest column instead
|
||||||
|
* overlapped the type text (the rest column shrinks below chip
|
||||||
|
* width, and a span onto an fr track never grows fixed tracks). */
|
||||||
|
grid-column: 9 / 11;
|
||||||
|
grid-row: 1;
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
.bf-type {
|
||||||
|
grid-row: 2;
|
||||||
|
max-width: 44vw;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.bf-recharge,
|
||||||
|
.bf-level,
|
||||||
|
.bf-predicted,
|
||||||
|
.bf-row .bf-mark {
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.bf-spark-line {
|
.bf-spark-line {
|
||||||
fill: none;
|
fill: none;
|
||||||
@@ -834,6 +959,18 @@ export class MaintenanceBatteryFleetSection extends LitElement {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 1px 4px 1px 10px;
|
padding: 1px 4px 1px 10px;
|
||||||
}
|
}
|
||||||
|
.bf-track-self {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bf-track-self input {
|
||||||
|
accent-color: var(--primary-color);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { LitElement, html, css, nothing } from "lit";
|
import { LitElement, html, css, nothing } from "lit";
|
||||||
import { property, state } from "lit/decorators.js";
|
import { property, state } from "lit/decorators.js";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { t, nativeFieldStyles } from "../styles";
|
import { langOf, t, nativeFieldStyles } from "../styles";
|
||||||
|
|
||||||
export interface ConfirmOptions {
|
export interface ConfirmOptions {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -90,7 +90,7 @@ export class MaintenanceConfirmDialog extends LitElement {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this._open) return nothing;
|
if (!this._open) return nothing;
|
||||||
const lang = this.hass?.language || "en";
|
const lang = langOf(this.hass);
|
||||||
return html`
|
return html`
|
||||||
<ha-dialog open @closed=${this._cancel}>
|
<ha-dialog open @closed=${this._cancel}>
|
||||||
<div class="dialog-title">${this._title}</div>
|
<div class="dialog-title">${this._title}</div>
|
||||||
|
|||||||
+3
-3
@@ -15,7 +15,7 @@ import { describeWsError } from "../ws-errors";
|
|||||||
import { downloadUrl } from "../helpers/download";
|
import { downloadUrl } from "../helpers/download";
|
||||||
import { downloadSignedDocument, openSignedDocument, signDocumentPath } from "../helpers/document-url";
|
import { downloadSignedDocument, openSignedDocument, signDocumentPath } from "../helpers/document-url";
|
||||||
import { formatBytes } from "../helpers/format-bytes";
|
import { formatBytes } from "../helpers/format-bytes";
|
||||||
import { CATEGORIES, CATEGORY_ICONS } from "../helpers/document-categories";
|
import { docDisplayName, CATEGORIES, CATEGORY_ICONS } from "../helpers/document-categories";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
interface MaintenanceDocument {
|
interface MaintenanceDocument {
|
||||||
@@ -263,7 +263,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _delete(doc: MaintenanceDocument): Promise<void> {
|
private async _delete(doc: MaintenanceDocument): Promise<void> {
|
||||||
const name = doc.title || doc.filename || doc.url || "";
|
const name = docDisplayName(doc);
|
||||||
if (!window.confirm(t("doc_delete_confirm", this._lang).replace("{name}", name))) return;
|
if (!window.confirm(t("doc_delete_confirm", this._lang).replace("{name}", name))) return;
|
||||||
this._busy = true;
|
this._busy = true;
|
||||||
this._error = "";
|
this._error = "";
|
||||||
@@ -447,7 +447,7 @@ export class MaintenanceDocumentsSection extends LitElement {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="doc-title">${doc.title || doc.filename || doc.url}</div>
|
<div class="doc-title">${docDisplayName(doc)}</div>
|
||||||
<div class="doc-meta">${meta}</div>
|
<div class="doc-meta">${meta}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="doc-row-actions">
|
<div class="doc-row-actions">
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { LitElement, html, css, nothing } from "lit";
|
|||||||
import { property, state } from "lit/decorators.js";
|
import { property, state } from "lit/decorators.js";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { t } from "../styles";
|
import { t } from "../styles";
|
||||||
|
import { downloadTextFile } from "../helpers/download";
|
||||||
|
|
||||||
interface QrResult {
|
interface QrResult {
|
||||||
svg_data_uri: string;
|
svg_data_uri: string;
|
||||||
@@ -194,16 +195,13 @@ ${safeSub ? `<div class="sub">${safeSub}</div>` : ""}
|
|||||||
const svgContent = decodeURIComponent(
|
const svgContent = decodeURIComponent(
|
||||||
result.svg_data_uri.replace("data:image/svg+xml,", ""),
|
result.svg_data_uri.replace("data:image/svg+xml,", ""),
|
||||||
);
|
);
|
||||||
const blob = new Blob([svgContent], { type: "image/svg+xml" });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = url;
|
|
||||||
const name = this._taskName
|
const name = this._taskName
|
||||||
? `${this._objectName}-${this._taskName}`
|
? `${this._objectName}-${this._taskName}`
|
||||||
: this._objectName;
|
: this._objectName;
|
||||||
a.download = `qr-${sanitizeFilename(name)}-${suffix}.svg`;
|
// helpers/download.ts, not a hand-rolled anchor: the detached-anchor +
|
||||||
a.click();
|
// immediate-revoke pattern this used silently fails in the Companion
|
||||||
URL.revokeObjectURL(url);
|
// WebView (drift audit 2026-08 caught the bypass).
|
||||||
|
downloadTextFile(svgContent, `qr-${sanitizeFilename(name)}-${suffix}.svg`, "image/svg+xml");
|
||||||
}
|
}
|
||||||
|
|
||||||
private _close(): void {
|
private _close(): void {
|
||||||
|
|||||||
+7
-2
@@ -7,10 +7,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { LitElement, html, css, nothing } from "lit";
|
import { LitElement, html, css, nothing } from "lit";
|
||||||
|
import { docDisplayName } from "../helpers/document-categories";
|
||||||
import { property, state } from "lit/decorators.js";
|
import { property, state } from "lit/decorators.js";
|
||||||
import { t, ensureLocale, langOf } from "../styles";
|
import { t, ensureLocale, langOf } from "../styles";
|
||||||
import { describeWsError } from "../ws-errors";
|
import { describeWsError } from "../ws-errors";
|
||||||
import { openSignedDocument } from "../helpers/document-url";
|
import { openSignedDocument } from "../helpers/document-url";
|
||||||
|
import { isSafeHttpUrl } from "../helpers/url";
|
||||||
import { formatBytes } from "../helpers/format-bytes";
|
import { formatBytes } from "../helpers/format-bytes";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
@@ -135,7 +137,10 @@ export class MaintenanceStorageSectionCard extends LitElement {
|
|||||||
|
|
||||||
private async _openResult(doc: SearchResult): Promise<void> {
|
private async _openResult(doc: SearchResult): Promise<void> {
|
||||||
if (doc.kind === "weblink") {
|
if (doc.kind === "weblink") {
|
||||||
window.open(doc.url, "_blank", "noopener");
|
// Defense-in-depth (mirrors task-documents/documents-section): never
|
||||||
|
// hand a non-http(s) weblink to window.open, even though the WS add
|
||||||
|
// path already rejects such schemes.
|
||||||
|
if (isSafeHttpUrl(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -150,7 +155,7 @@ export class MaintenanceStorageSectionCard extends LitElement {
|
|||||||
<div class="obj-row result-row" title=${t("doc_open", L)} @click=${() => this._openResult(doc)}>
|
<div class="obj-row result-row" title=${t("doc_open", L)} @click=${() => this._openResult(doc)}>
|
||||||
<ha-icon icon=${doc.kind === "weblink" ? "mdi:link-variant" : "mdi:file-document-outline"}></ha-icon>
|
<ha-icon icon=${doc.kind === "weblink" ? "mdi:link-variant" : "mdi:file-document-outline"}></ha-icon>
|
||||||
<div class="result-info">
|
<div class="result-info">
|
||||||
<div class="result-title">${doc.title || doc.filename || doc.url}</div>
|
<div class="result-title">${docDisplayName(doc)}</div>
|
||||||
<div class="result-obj">${doc.object_name}</div>
|
<div class="result-obj">${doc.object_name}</div>
|
||||||
</div>
|
</div>
|
||||||
<ha-icon class="result-open" icon=${doc.kind === "weblink" ? "mdi:open-in-new" : "mdi:eye-outline"}></ha-icon>
|
<ha-icon class="result-open" icon=${doc.kind === "weblink" ? "mdi:open-in-new" : "mdi:eye-outline"}></ha-icon>
|
||||||
|
|||||||
@@ -1149,6 +1149,8 @@ export class MaintenanceTaskDialog extends LitElement {
|
|||||||
if (this._triggerFromState) triggerConfig.trigger_from_state = this._triggerFromState;
|
if (this._triggerFromState) triggerConfig.trigger_from_state = this._triggerFromState;
|
||||||
if (this._triggerToState) triggerConfig.trigger_to_state = this._triggerToState;
|
if (this._triggerToState) triggerConfig.trigger_to_state = this._triggerToState;
|
||||||
if (this._triggerTargetChanges) { const v = parseInt(this._triggerTargetChanges, 10); if (!isNaN(v)) triggerConfig.trigger_target_changes = v; }
|
if (this._triggerTargetChanges) { const v = parseInt(this._triggerTargetChanges, 10); if (!isNaN(v)) triggerConfig.trigger_target_changes = v; }
|
||||||
|
// #136: the new state must hold this long before a change counts.
|
||||||
|
if (this._triggerForMinutes) { const v = parseInt(this._triggerForMinutes, 10); if (!isNaN(v)) triggerConfig.trigger_for_minutes = v; }
|
||||||
} else if (this._triggerType === "runtime") {
|
} else if (this._triggerType === "runtime") {
|
||||||
if (this._triggerRuntimeHours) { const v = parseFloat(this._triggerRuntimeHours); if (!isNaN(v)) triggerConfig.trigger_runtime_hours = v; }
|
if (this._triggerRuntimeHours) { const v = parseFloat(this._triggerRuntimeHours); if (!isNaN(v)) triggerConfig.trigger_runtime_hours = v; }
|
||||||
const onStates = this._triggerOnStates.split(",").map((s) => s.trim()).filter(Boolean);
|
const onStates = this._triggerOnStates.split(",").map((s) => s.trim()).filter(Boolean);
|
||||||
@@ -2246,6 +2248,14 @@ export class MaintenanceTaskDialog extends LitElement {
|
|||||||
@input=${(e: Event) => (this._triggerTargetChanges = (e.target as HTMLInputElement).value)}
|
@input=${(e: Event) => (this._triggerTargetChanges = (e.target as HTMLInputElement).value)}
|
||||||
></ms-textfield>
|
></ms-textfield>
|
||||||
<div class="field-help">${t("target_changes_help", L)}</div>
|
<div class="field-help">${t("target_changes_help", L)}</div>
|
||||||
|
<ms-textfield
|
||||||
|
label="${t("for_at_least_minutes", L)}"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
.value=${this._triggerForMinutes}
|
||||||
|
@input=${(e: Event) => (this._triggerForMinutes = (e.target as HTMLInputElement).value)}
|
||||||
|
></ms-textfield>
|
||||||
|
<div class="field-help">${t("for_minutes_state_help", L)}</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
if (this._triggerType === "runtime") {
|
if (this._triggerType === "runtime") {
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import { t, ensureLocale, langOf } from "../styles";
|
|||||||
import { describeWsError } from "../ws-errors";
|
import { describeWsError } from "../ws-errors";
|
||||||
import { downloadUrl } from "../helpers/download";
|
import { downloadUrl } from "../helpers/download";
|
||||||
import { downloadSignedDocument, openSignedDocument } from "../helpers/document-url";
|
import { downloadSignedDocument, openSignedDocument } from "../helpers/document-url";
|
||||||
|
import { isSafeHttpUrl } from "../helpers/url";
|
||||||
import { formatBytes } from "../helpers/format-bytes";
|
import { formatBytes } from "../helpers/format-bytes";
|
||||||
import { CATEGORIES, CATEGORY_ICONS } from "../helpers/document-categories";
|
import { docDisplayName, CATEGORIES, CATEGORY_ICONS } from "../helpers/document-categories";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
|
|
||||||
interface Doc {
|
interface Doc {
|
||||||
@@ -144,7 +145,11 @@ export class MaintenanceTaskDocuments extends LitElement {
|
|||||||
|
|
||||||
private async _open(doc: Doc): Promise<void> {
|
private async _open(doc: Doc): Promise<void> {
|
||||||
if (doc.kind === "weblink") {
|
if (doc.kind === "weblink") {
|
||||||
window.open(doc.url, "_blank", "noopener");
|
// Defense-in-depth: the add-link WS path already refuses non-http(s)
|
||||||
|
// schemes, but a weblink stored before that check (or via a future
|
||||||
|
// path) must never reach window.open as javascript:/data: — guard the
|
||||||
|
// sink too, like documents-section does.
|
||||||
|
if (isSafeHttpUrl(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// A per-task page hint jumps straight to the relevant page via the PDF
|
// A per-task page hint jumps straight to the relevant page via the PDF
|
||||||
@@ -206,7 +211,7 @@ export class MaintenanceTaskDocuments extends LitElement {
|
|||||||
>
|
>
|
||||||
<option value="" ?selected=${!this._attachId}>${t("doc_link_existing", L)}</option>
|
<option value="" ?selected=${!this._attachId}>${t("doc_link_existing", L)}</option>
|
||||||
${available.map(
|
${available.map(
|
||||||
(d) => html`<option value=${d.id} ?selected=${d.id === this._attachId}>${d.title || d.filename || d.url}</option>`,
|
(d) => html`<option value=${d.id} ?selected=${d.id === this._attachId}>${docDisplayName(d)}</option>`,
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
<button class="tdoc-btn" ?disabled=${this._busy || !this._attachId} @click=${this._link}>
|
<button class="tdoc-btn" ?disabled=${this._busy || !this._attachId} @click=${this._link}>
|
||||||
@@ -240,7 +245,7 @@ export class MaintenanceTaskDocuments extends LitElement {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="tdoc-title">${doc.title || doc.filename || doc.url}</div>
|
<div class="tdoc-title">${docDisplayName(doc)}</div>
|
||||||
<div class="tdoc-meta">
|
<div class="tdoc-meta">
|
||||||
${meta}${page ? html` · <span class="tdoc-pagetag">${t("doc_page", L)} ${page}</span>` : nothing}
|
${meta}${page ? html` · <span class="tdoc-pagetag">${t("doc_page", L)} ${page}</span>` : nothing}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import type { MaintenanceQrDialog } from "./components/qr-dialog";
|
|||||||
import type { MaintenanceTaskQuickActionsDialog } from "./components/task-quick-actions-dialog";
|
import type { MaintenanceTaskQuickActionsDialog } from "./components/task-quick-actions-dialog";
|
||||||
import type { MaintenanceObjectQuickActionsDialog } from "./components/object-quick-actions-dialog";
|
import type { MaintenanceObjectQuickActionsDialog } from "./components/object-quick-actions-dialog";
|
||||||
import type { HomeAssistant, MaintenanceObject } from "./types";
|
import type { HomeAssistant, MaintenanceObject } from "./types";
|
||||||
import { ensureLocale, isLocaleLoaded } from "./styles";
|
import { ensureLocale, isLocaleLoaded, langOf } from "./styles";
|
||||||
|
|
||||||
const OBJECT_DIALOG_TAG = "maintenance-object-dialog";
|
const OBJECT_DIALOG_TAG = "maintenance-object-dialog";
|
||||||
const TASK_DIALOG_TAG = "maintenance-task-dialog";
|
const TASK_DIALOG_TAG = "maintenance-task-dialog";
|
||||||
@@ -91,7 +91,7 @@ function syncHass(el: HTMLElement & { hass?: HomeAssistant }): boolean {
|
|||||||
// in English even when the rest of HA is localized. Fetch the user's locale
|
// in English even when the rest of HA is localized. Fetch the user's locale
|
||||||
// (idempotent + cached) and force a re-render once it's in, so the first paint
|
// (idempotent + cached) and force a re-render once it's in, so the first paint
|
||||||
// (which may be English) is corrected to the user's language.
|
// (which may be English) is corrected to the user's language.
|
||||||
const lang = hass.language || "en";
|
const lang = langOf(hass);
|
||||||
if (!isLocaleLoaded(lang)) {
|
if (!isLocaleLoaded(lang)) {
|
||||||
void ensureLocale(lang).then(() => {
|
void ensureLocale(lang).then(() => {
|
||||||
(el as unknown as { requestUpdate?: () => void }).requestUpdate?.();
|
(el as unknown as { requestUpdate?: () => void }).requestUpdate?.();
|
||||||
|
|||||||
@@ -279,6 +279,10 @@ const HISTORY_TYPE_TO_STATUS: Record<string, string> = {
|
|||||||
completed: "ok",
|
completed: "ok",
|
||||||
reset: "ok",
|
reset: "ok",
|
||||||
skipped: "due_soon",
|
skipped: "due_soon",
|
||||||
|
// A cycle that was NOT done in time (skip-while-overdue writes it, and the
|
||||||
|
// coordinator stamps it automatically) — must never paint green. Found by
|
||||||
|
// the 2026-08 drift audit: the missing entry fell through to "ok".
|
||||||
|
missed: "overdue",
|
||||||
triggered: "triggered",
|
triggered: "triggered",
|
||||||
trigger_replaced: "triggered",
|
trigger_replaced: "triggered",
|
||||||
trigger_removed: "ok", // config change, not a due/triggered event
|
trigger_removed: "ok", // config change, not a due/triggered event
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Seskupit podle",
|
"group_by_label": "Seskupit podle",
|
||||||
"state_value_help": "Použijte hodnotu stavu HA (obvykle malými písmeny, např. \"on\"/\"off\"). Velikost písmen se při uložení normalizuje.",
|
"state_value_help": "Použijte hodnotu stavu HA (obvykle malými písmeny, např. \"on\"/\"off\"). Velikost písmen se při uložení normalizuje.",
|
||||||
"target_changes_help": "Počet odpovídajících přechodů, po kterých se trigger spustí (výchozí: 1).",
|
"target_changes_help": "Počet odpovídajících přechodů, po kterých se trigger spustí (výchozí: 1).",
|
||||||
|
"for_minutes_state_help": "0 počítá každou změnu ihned. S minutami musí nový stav nejprve vydržet — krátké bliknutí pak nespustí ani se nepočítá.",
|
||||||
"qr_print_title": "Tisk QR kódů",
|
"qr_print_title": "Tisk QR kódů",
|
||||||
"qr_print_desc": "Vygeneruj tiskovou stránku s QR kódy k vystřižení a nalepení na zařízení.",
|
"qr_print_desc": "Vygeneruj tiskovou stránku s QR kódy k vystřižení a nalepení na zařízení.",
|
||||||
"qr_print_load": "Načíst objekty",
|
"qr_print_load": "Načíst objekty",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Nebylo potřeba",
|
"quick_complete_defaults_feedback_not_needed": "Nebylo potřeba",
|
||||||
"quick_complete_success": "Rychle označeno jako hotové",
|
"quick_complete_success": "Rychle označeno jako hotové",
|
||||||
"trigger_replaced": "Spouštěč nahrazen",
|
"trigger_replaced": "Spouštěč nahrazen",
|
||||||
|
"trigger_removed": "Spouštěč odebrán",
|
||||||
"add": "Přidat",
|
"add": "Přidat",
|
||||||
"show_stats": "Zobrazit statistiky + grafy",
|
"show_stats": "Zobrazit statistiky + grafy",
|
||||||
"hide_stats": "Skrýt statistiky",
|
"hide_stats": "Skrýt statistiky",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Za předpokladu včasného dokončení.",
|
"schedule_preview_ontime": "Za předpokladu včasného dokončení.",
|
||||||
"schedule_preview_ends": "(série končí)",
|
"schedule_preview_ends": "(série končí)",
|
||||||
"adopt_problem_responsible": "Odpovědná osoba pro všechny převzaté úkoly (volitelné)",
|
"adopt_problem_responsible": "Odpovědná osoba pro všechny převzaté úkoly (volitelné)",
|
||||||
|
"adopt_for_minutes_hint": "Spustit až když problém trvá takto dlouho — 0 reaguje na první bliknutí.",
|
||||||
"adopt_problem_configure": "Konfigurovat",
|
"adopt_problem_configure": "Konfigurovat",
|
||||||
"history_auto": "Automaticky",
|
"history_auto": "Automaticky",
|
||||||
"battery_fleet_title": "Flotila baterií",
|
"battery_fleet_title": "Flotila baterií",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Vylučte zařízení už dřív, než vůbec ohlásí nízkou úroveň — vysavač, který se sám nabíjí, nebo telefon, který varuje sám.",
|
"battery_fleet_all_hint": "Vylučte zařízení už dřív, než vůbec ohlásí nízkou úroveň — vysavač, který se sám nabíjí, nebo telefon, který varuje sám.",
|
||||||
"battery_fleet_add": "Přidat baterii",
|
"battery_fleet_add": "Přidat baterii",
|
||||||
"battery_fleet_add_hint": "Vyberte senzor baterie, který automatická detekce přehlédla — okamžitě se objeví v seznamu.",
|
"battery_fleet_add_hint": "Vyberte senzor baterie, který automatická detekce přehlédla — okamžitě se objeví v seznamu.",
|
||||||
|
"battery_fleet_track_self": "Sledovat samodobíjecí baterie",
|
||||||
|
"battery_fleet_track_self_hint": "Telefony, vysavače a další samodobíjecí zařízení se zobrazují jako akumulátory — při nízkém stavu žádají o nabití, nikdy o nové články.",
|
||||||
"battery_fleet_status_low": "Nízká",
|
"battery_fleet_status_low": "Nízká",
|
||||||
"battery_fleet_status_soon": "Brzy",
|
"battery_fleet_status_soon": "Brzy",
|
||||||
"battery_fleet_status_ok": "V pořádku",
|
"battery_fleet_status_ok": "V pořádku",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Snart forfalden",
|
"due_soon": "Snart forfalden",
|
||||||
"triggered": "Udløst",
|
"triggered": "Udløst",
|
||||||
"trigger_replaced": "Udløser erstattet",
|
"trigger_replaced": "Udløser erstattet",
|
||||||
|
"trigger_removed": "Udløser fjernet",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"new_object": "+ Nyt objekt",
|
"new_object": "+ Nyt objekt",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Grupper efter",
|
"group_by_label": "Grupper efter",
|
||||||
"state_value_help": "Brug HA-tilstandsværdien (normalt med små bogstaver, f.eks. \"on\"/\"off\"). Store/små bogstaver normaliseres ved lagring.",
|
"state_value_help": "Brug HA-tilstandsværdien (normalt med små bogstaver, f.eks. \"on\"/\"off\"). Store/små bogstaver normaliseres ved lagring.",
|
||||||
"target_changes_help": "Antal matchende overgange, før udløseren aktiveres (standard: 1).",
|
"target_changes_help": "Antal matchende overgange, før udløseren aktiveres (standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 tæller hver ændring straks. Med minutter skal den nye tilstand først holde — korte blink hverken udløser eller tælles så.",
|
||||||
"qr_print_title": "Udskriv QR-koder",
|
"qr_print_title": "Udskriv QR-koder",
|
||||||
"qr_print_desc": "Generer en udskriftsklar side med QR-koder, der kan klippes ud og sættes på dit udstyr.",
|
"qr_print_desc": "Generer en udskriftsklar side med QR-koder, der kan klippes ud og sættes på dit udstyr.",
|
||||||
"qr_print_load": "Indlæs objekter",
|
"qr_print_load": "Indlæs objekter",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Forudsat rettidig fuldførelse.",
|
"schedule_preview_ontime": "Forudsat rettidig fuldførelse.",
|
||||||
"schedule_preview_ends": "(serien slutter)",
|
"schedule_preview_ends": "(serien slutter)",
|
||||||
"adopt_problem_responsible": "Ansvarlig bruger for alle overtagne opgaver (valgfrit)",
|
"adopt_problem_responsible": "Ansvarlig bruger for alle overtagne opgaver (valgfrit)",
|
||||||
|
"adopt_for_minutes_hint": "Udløs først når problemet har varet så længe — 0 reagerer på første blink.",
|
||||||
"adopt_problem_configure": "Konfigurer",
|
"adopt_problem_configure": "Konfigurer",
|
||||||
"history_auto": "Automatisk",
|
"history_auto": "Automatisk",
|
||||||
"battery_fleet_title": "Batteriflåde",
|
"battery_fleet_title": "Batteriflåde",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Udeluk en enhed her, før den overhovedet melder lavt niveau — en støvsuger, der lader sig selv, eller en telefon, der advarer af sig selv.",
|
"battery_fleet_all_hint": "Udeluk en enhed her, før den overhovedet melder lavt niveau — en støvsuger, der lader sig selv, eller en telefon, der advarer af sig selv.",
|
||||||
"battery_fleet_add": "Tilføj et batteri",
|
"battery_fleet_add": "Tilføj et batteri",
|
||||||
"battery_fleet_add_hint": "Vælg en batterisensor, som den automatiske registrering overså — den kommer straks på listen.",
|
"battery_fleet_add_hint": "Vælg en batterisensor, som den automatiske registrering overså — den kommer straks på listen.",
|
||||||
|
"battery_fleet_track_self": "Spor selvopladende batterier",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoner, støvsugere og andre selvopladende enheder vises som genopladelige — ved lavt niveau bedes om opladning, aldrig om nye batterier.",
|
||||||
"battery_fleet_status_low": "Lav",
|
"battery_fleet_status_low": "Lav",
|
||||||
"battery_fleet_status_soon": "Snart",
|
"battery_fleet_status_soon": "Snart",
|
||||||
"battery_fleet_status_ok": "I orden",
|
"battery_fleet_status_ok": "I orden",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Bald fällig",
|
"due_soon": "Bald fällig",
|
||||||
"triggered": "Ausgelöst",
|
"triggered": "Ausgelöst",
|
||||||
"trigger_replaced": "Auslöser ersetzt",
|
"trigger_replaced": "Auslöser ersetzt",
|
||||||
|
"trigger_removed": "Trigger entfernt",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"new_object": "+ Neues Objekt",
|
"new_object": "+ Neues Objekt",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Gruppieren nach",
|
"group_by_label": "Gruppieren nach",
|
||||||
"state_value_help": "Verwende den HA-Zustandswert (meist kleingeschrieben, z. B. \"on\"/\"off\"). Groß-/Kleinschreibung wird beim Speichern normalisiert.",
|
"state_value_help": "Verwende den HA-Zustandswert (meist kleingeschrieben, z. B. \"on\"/\"off\"). Groß-/Kleinschreibung wird beim Speichern normalisiert.",
|
||||||
"target_changes_help": "Anzahl der passenden Übergänge, nach denen der Trigger auslöst (Standard: 1).",
|
"target_changes_help": "Anzahl der passenden Übergänge, nach denen der Trigger auslöst (Standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 zählt jede Änderung sofort. Mit Minuten muss der neue Zustand erst so lange bestehen — kurzes Flackern löst dann weder aus noch zählt es.",
|
||||||
"qr_print_title": "QR-Codes drucken",
|
"qr_print_title": "QR-Codes drucken",
|
||||||
"qr_print_desc": "Erzeuge eine Druckseite mit QR-Codes zum Ausschneiden und Anbringen an den Geräten.",
|
"qr_print_desc": "Erzeuge eine Druckseite mit QR-Codes zum Ausschneiden und Anbringen an den Geräten.",
|
||||||
"qr_print_load": "Objekte laden",
|
"qr_print_load": "Objekte laden",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Bei pünktlichem Abschluss.",
|
"schedule_preview_ontime": "Bei pünktlichem Abschluss.",
|
||||||
"schedule_preview_ends": "(Serie endet)",
|
"schedule_preview_ends": "(Serie endet)",
|
||||||
"adopt_problem_responsible": "Verantwortliche Person für alle übernommenen Aufgaben (optional)",
|
"adopt_problem_responsible": "Verantwortliche Person für alle übernommenen Aufgaben (optional)",
|
||||||
|
"adopt_for_minutes_hint": "Erst auslösen, wenn das Problem so lange besteht — 0 reagiert auf das erste Flackern.",
|
||||||
"adopt_problem_configure": "Konfigurieren",
|
"adopt_problem_configure": "Konfigurieren",
|
||||||
"history_auto": "Automatisch",
|
"history_auto": "Automatisch",
|
||||||
"battery_fleet_title": "Batterie-Flotte",
|
"battery_fleet_title": "Batterie-Flotte",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Ein Gerät hier ausschließen, bevor es überhaupt schwach meldet — etwa ein Sauger, der sich selbst lädt, oder ein Telefon, das von sich aus warnt.",
|
"battery_fleet_all_hint": "Ein Gerät hier ausschließen, bevor es überhaupt schwach meldet — etwa ein Sauger, der sich selbst lädt, oder ein Telefon, das von sich aus warnt.",
|
||||||
"battery_fleet_add": "Batterie hinzufügen",
|
"battery_fleet_add": "Batterie hinzufügen",
|
||||||
"battery_fleet_add_hint": "Einen Batteriesensor wählen, den die automatische Erkennung übersehen hat — er erscheint sofort in der Liste.",
|
"battery_fleet_add_hint": "Einen Batteriesensor wählen, den die automatische Erkennung übersehen hat — er erscheint sofort in der Liste.",
|
||||||
|
"battery_fleet_track_self": "Selbstladende Batterien mitverfolgen",
|
||||||
|
"battery_fleet_track_self_hint": "Telefone, Staubsauger und andere selbstladende Geräte erscheinen als Akkus — bei niedrigem Stand wird ans Laden erinnert, nie an neue Zellen.",
|
||||||
"battery_fleet_status_low": "Schwach",
|
"battery_fleet_status_low": "Schwach",
|
||||||
"battery_fleet_status_soon": "Bald",
|
"battery_fleet_status_soon": "Bald",
|
||||||
"battery_fleet_status_ok": "In Ordnung",
|
"battery_fleet_status_ok": "In Ordnung",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Due Soon",
|
"due_soon": "Due Soon",
|
||||||
"triggered": "Triggered",
|
"triggered": "Triggered",
|
||||||
"trigger_replaced": "Trigger replaced",
|
"trigger_replaced": "Trigger replaced",
|
||||||
|
"trigger_removed": "Trigger removed",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"new_object": "+ New Object",
|
"new_object": "+ New Object",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Group by",
|
"group_by_label": "Group by",
|
||||||
"state_value_help": "Use the HA state value (usually lowercase, e.g. \"on\"/\"off\"). Case is normalised on save.",
|
"state_value_help": "Use the HA state value (usually lowercase, e.g. \"on\"/\"off\"). Case is normalised on save.",
|
||||||
"target_changes_help": "Number of matching transitions before the trigger fires (default: 1).",
|
"target_changes_help": "Number of matching transitions before the trigger fires (default: 1).",
|
||||||
|
"for_minutes_state_help": "0 counts every change immediately. Set minutes and the new state must hold that long first — brief flickers then neither trigger nor count.",
|
||||||
"qr_print_title": "Print QR codes",
|
"qr_print_title": "Print QR codes",
|
||||||
"qr_print_desc": "Generate a printable page of QR codes to cut out and stick on your equipment.",
|
"qr_print_desc": "Generate a printable page of QR codes to cut out and stick on your equipment.",
|
||||||
"qr_print_load": "Load objects",
|
"qr_print_load": "Load objects",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Assuming on-time completion.",
|
"schedule_preview_ontime": "Assuming on-time completion.",
|
||||||
"schedule_preview_ends": "(series ends)",
|
"schedule_preview_ends": "(series ends)",
|
||||||
"adopt_problem_responsible": "Responsible user for all adopted tasks (optional)",
|
"adopt_problem_responsible": "Responsible user for all adopted tasks (optional)",
|
||||||
|
"adopt_for_minutes_hint": "Only trigger once the problem has persisted this long — 0 reacts to the first flicker.",
|
||||||
"adopt_problem_configure": "Configure",
|
"adopt_problem_configure": "Configure",
|
||||||
"history_auto": "Automatic",
|
"history_auto": "Automatic",
|
||||||
"battery_fleet_title": "Battery fleet",
|
"battery_fleet_title": "Battery fleet",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Exclude a device here to drop it from the fleet before it ever reports low — a vacuum that recharges itself, or a phone that warns you on its own.",
|
"battery_fleet_all_hint": "Exclude a device here to drop it from the fleet before it ever reports low — a vacuum that recharges itself, or a phone that warns you on its own.",
|
||||||
"battery_fleet_add": "Add a battery",
|
"battery_fleet_add": "Add a battery",
|
||||||
"battery_fleet_add_hint": "Pick a battery sensor the automatic discovery missed — it joins the roster immediately.",
|
"battery_fleet_add_hint": "Pick a battery sensor the automatic discovery missed — it joins the roster immediately.",
|
||||||
|
"battery_fleet_track_self": "Track self-charging batteries",
|
||||||
|
"battery_fleet_track_self_hint": "Phones, vacuums and other devices that recharge themselves appear as rechargeables — a low one asks for a charge, never for new cells.",
|
||||||
"battery_fleet_status_low": "Low",
|
"battery_fleet_status_low": "Low",
|
||||||
"battery_fleet_status_soon": "Soon",
|
"battery_fleet_status_soon": "Soon",
|
||||||
"battery_fleet_status_ok": "Healthy",
|
"battery_fleet_status_ok": "Healthy",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Agrupar por",
|
"group_by_label": "Agrupar por",
|
||||||
"state_value_help": "Usa el valor de estado de HA (normalmente en minúsculas, p. ej. \"on\"/\"off\"). Las mayúsculas se normalizan al guardar.",
|
"state_value_help": "Usa el valor de estado de HA (normalmente en minúsculas, p. ej. \"on\"/\"off\"). Las mayúsculas se normalizan al guardar.",
|
||||||
"target_changes_help": "Número de transiciones coincidentes antes de que se dispare el trigger (predeterminado: 1).",
|
"target_changes_help": "Número de transiciones coincidentes antes de que se dispare el trigger (predeterminado: 1).",
|
||||||
|
"for_minutes_state_help": "0 cuenta cada cambio de inmediato. Con minutos, el nuevo estado debe mantenerse primero — un parpadeo breve entonces ni dispara ni cuenta.",
|
||||||
"qr_print_title": "Imprimir códigos QR",
|
"qr_print_title": "Imprimir códigos QR",
|
||||||
"qr_print_desc": "Genera una página imprimible de códigos QR para recortar y pegar en tus equipos.",
|
"qr_print_desc": "Genera una página imprimible de códigos QR para recortar y pegar en tus equipos.",
|
||||||
"qr_print_load": "Cargar objetos",
|
"qr_print_load": "Cargar objetos",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "No era necesario",
|
"quick_complete_defaults_feedback_not_needed": "No era necesario",
|
||||||
"quick_complete_success": "Completado rápido",
|
"quick_complete_success": "Completado rápido",
|
||||||
"trigger_replaced": "Disparador reemplazado",
|
"trigger_replaced": "Disparador reemplazado",
|
||||||
|
"trigger_removed": "Disparador eliminado",
|
||||||
"add": "Añadir",
|
"add": "Añadir",
|
||||||
"show_stats": "Mostrar estadísticas + gráficos",
|
"show_stats": "Mostrar estadísticas + gráficos",
|
||||||
"hide_stats": "Ocultar estadísticas",
|
"hide_stats": "Ocultar estadísticas",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Suponiendo finalización puntual.",
|
"schedule_preview_ontime": "Suponiendo finalización puntual.",
|
||||||
"schedule_preview_ends": "(la serie termina)",
|
"schedule_preview_ends": "(la serie termina)",
|
||||||
"adopt_problem_responsible": "Usuario responsable para todas las tareas adoptadas (opcional)",
|
"adopt_problem_responsible": "Usuario responsable para todas las tareas adoptadas (opcional)",
|
||||||
|
"adopt_for_minutes_hint": "Disparar solo si el problema persiste este tiempo — 0 reacciona al primer parpadeo.",
|
||||||
"adopt_problem_configure": "Configurar",
|
"adopt_problem_configure": "Configurar",
|
||||||
"history_auto": "Automático",
|
"history_auto": "Automático",
|
||||||
"battery_fleet_title": "Flota de baterías",
|
"battery_fleet_title": "Flota de baterías",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Excluye aquí un dispositivo antes de que llegue a avisar de carga baja — un aspirador que se recarga solo o un teléfono que ya avisa por su cuenta.",
|
"battery_fleet_all_hint": "Excluye aquí un dispositivo antes de que llegue a avisar de carga baja — un aspirador que se recarga solo o un teléfono que ya avisa por su cuenta.",
|
||||||
"battery_fleet_add": "Añadir una batería",
|
"battery_fleet_add": "Añadir una batería",
|
||||||
"battery_fleet_add_hint": "Elige un sensor de batería que la detección automática pasó por alto — se une a la lista de inmediato.",
|
"battery_fleet_add_hint": "Elige un sensor de batería que la detección automática pasó por alto — se une a la lista de inmediato.",
|
||||||
|
"battery_fleet_track_self": "Seguir baterías de carga automática",
|
||||||
|
"battery_fleet_track_self_hint": "Teléfonos, aspiradoras y otros dispositivos que se recargan solos aparecen como recargables: si están bajos piden una carga, nunca pilas nuevas.",
|
||||||
"battery_fleet_status_low": "Baja",
|
"battery_fleet_status_low": "Baja",
|
||||||
"battery_fleet_status_soon": "Pronto",
|
"battery_fleet_status_soon": "Pronto",
|
||||||
"battery_fleet_status_ok": "Correcta",
|
"battery_fleet_status_ok": "Correcta",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Pian erääntyy",
|
"due_soon": "Pian erääntyy",
|
||||||
"triggered": "Laukaistu",
|
"triggered": "Laukaistu",
|
||||||
"trigger_replaced": "Laukaisin korvattu",
|
"trigger_replaced": "Laukaisin korvattu",
|
||||||
|
"trigger_removed": "Laukaisin poistettu",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Kaikki",
|
"all": "Kaikki",
|
||||||
"new_object": "+ Uusi kohde",
|
"new_object": "+ Uusi kohde",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Ryhmittele",
|
"group_by_label": "Ryhmittele",
|
||||||
"state_value_help": "Käytä HA:n tila-arvoa (yleensä pienillä kirjaimilla, esim. \"on\"/\"off\"). Kirjainkoko normalisoidaan tallennettaessa.",
|
"state_value_help": "Käytä HA:n tila-arvoa (yleensä pienillä kirjaimilla, esim. \"on\"/\"off\"). Kirjainkoko normalisoidaan tallennettaessa.",
|
||||||
"target_changes_help": "Vastaavien siirtymien määrä ennen laukaisua (oletus: 1).",
|
"target_changes_help": "Vastaavien siirtymien määrä ennen laukaisua (oletus: 1).",
|
||||||
|
"for_minutes_state_help": "0 laskee jokaisen muutoksen heti. Minuuteilla uuden tilan on ensin pysyttävä — lyhyet välähdykset eivät silloin laukaise eivätkä kirjaudu.",
|
||||||
"qr_print_title": "Tulosta QR-koodit",
|
"qr_print_title": "Tulosta QR-koodit",
|
||||||
"qr_print_desc": "Luo tulostettava sivu QR-koodeja, jotka voit leikata ja kiinnittää laitteisiin.",
|
"qr_print_desc": "Luo tulostettava sivu QR-koodeja, jotka voit leikata ja kiinnittää laitteisiin.",
|
||||||
"qr_print_load": "Lataa kohteet",
|
"qr_print_load": "Lataa kohteet",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Olettaen, että valmistuminen on ajallaan.",
|
"schedule_preview_ontime": "Olettaen, että valmistuminen on ajallaan.",
|
||||||
"schedule_preview_ends": "(sarja päättyy)",
|
"schedule_preview_ends": "(sarja päättyy)",
|
||||||
"adopt_problem_responsible": "Vastuuhenkilö kaikille omaksutuille tehtäville (valinnainen)",
|
"adopt_problem_responsible": "Vastuuhenkilö kaikille omaksutuille tehtäville (valinnainen)",
|
||||||
|
"adopt_for_minutes_hint": "Laukaise vasta kun ongelma on kestänyt näin kauan — 0 reagoi ensimmäiseen välähdykseen.",
|
||||||
"adopt_problem_configure": "Määritä",
|
"adopt_problem_configure": "Määritä",
|
||||||
"history_auto": "Automaattinen",
|
"history_auto": "Automaattinen",
|
||||||
"battery_fleet_title": "Paristokanta",
|
"battery_fleet_title": "Paristokanta",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Sulje laite pois jo ennen kuin se ilmoittaa alhaisesta varauksesta — imuri, joka lataa itsensä, tai puhelin, joka varoittaa itse.",
|
"battery_fleet_all_hint": "Sulje laite pois jo ennen kuin se ilmoittaa alhaisesta varauksesta — imuri, joka lataa itsensä, tai puhelin, joka varoittaa itse.",
|
||||||
"battery_fleet_add": "Lisää paristo",
|
"battery_fleet_add": "Lisää paristo",
|
||||||
"battery_fleet_add_hint": "Valitse paristoanturi, jonka automaattinen tunnistus ohitti — se liittyy luetteloon heti.",
|
"battery_fleet_add_hint": "Valitse paristoanturi, jonka automaattinen tunnistus ohitti — se liittyy luetteloon heti.",
|
||||||
|
"battery_fleet_track_self": "Seuraa itselatautuvia akkuja",
|
||||||
|
"battery_fleet_track_self_hint": "Puhelimet, imurit ja muut itselatautuvat laitteet näkyvät ladattavina — vähissä oleva pyytää latausta, ei koskaan uusia paristoja.",
|
||||||
"battery_fleet_status_low": "Vähissä",
|
"battery_fleet_status_low": "Vähissä",
|
||||||
"battery_fleet_status_soon": "Pian",
|
"battery_fleet_status_soon": "Pian",
|
||||||
"battery_fleet_status_ok": "Kunnossa",
|
"battery_fleet_status_ok": "Kunnossa",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Grouper par",
|
"group_by_label": "Grouper par",
|
||||||
"state_value_help": "Utilisez la valeur d'état HA (généralement en minuscules, p. ex. \"on\"/\"off\"). La casse est normalisée à l'enregistrement.",
|
"state_value_help": "Utilisez la valeur d'état HA (généralement en minuscules, p. ex. \"on\"/\"off\"). La casse est normalisée à l'enregistrement.",
|
||||||
"target_changes_help": "Nombre de transitions correspondantes avant le déclenchement (par défaut : 1).",
|
"target_changes_help": "Nombre de transitions correspondantes avant le déclenchement (par défaut : 1).",
|
||||||
|
"for_minutes_state_help": "0 compte chaque changement immédiatement. Avec des minutes, le nouvel état doit d'abord se maintenir — un bref clignotement ne déclenche ni ne compte alors.",
|
||||||
"qr_print_title": "Imprimer les QR codes",
|
"qr_print_title": "Imprimer les QR codes",
|
||||||
"qr_print_desc": "Générer une page imprimable de QR codes à découper et coller sur votre équipement.",
|
"qr_print_desc": "Générer une page imprimable de QR codes à découper et coller sur votre équipement.",
|
||||||
"qr_print_load": "Charger les objets",
|
"qr_print_load": "Charger les objets",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Non nécessaire",
|
"quick_complete_defaults_feedback_not_needed": "Non nécessaire",
|
||||||
"quick_complete_success": "Terminé rapidement",
|
"quick_complete_success": "Terminé rapidement",
|
||||||
"trigger_replaced": "Déclencheur remplacé",
|
"trigger_replaced": "Déclencheur remplacé",
|
||||||
|
"trigger_removed": "Déclencheur supprimé",
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
"show_stats": "Afficher les statistiques + graphiques",
|
"show_stats": "Afficher les statistiques + graphiques",
|
||||||
"hide_stats": "Masquer les statistiques",
|
"hide_stats": "Masquer les statistiques",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "En supposant un achèvement ponctuel.",
|
"schedule_preview_ontime": "En supposant un achèvement ponctuel.",
|
||||||
"schedule_preview_ends": "(fin de la série)",
|
"schedule_preview_ends": "(fin de la série)",
|
||||||
"adopt_problem_responsible": "Personne responsable pour toutes les tâches adoptées (optionnel)",
|
"adopt_problem_responsible": "Personne responsable pour toutes les tâches adoptées (optionnel)",
|
||||||
|
"adopt_for_minutes_hint": "Ne déclencher que si le problème persiste aussi longtemps — 0 réagit au premier clignotement.",
|
||||||
"adopt_problem_configure": "Configurer",
|
"adopt_problem_configure": "Configurer",
|
||||||
"history_auto": "Automatique",
|
"history_auto": "Automatique",
|
||||||
"battery_fleet_title": "Parc de piles",
|
"battery_fleet_title": "Parc de piles",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Excluez un appareil ici avant même qu'il ne signale une charge faible — un aspirateur qui se recharge seul, ou un téléphone qui vous prévient lui-même.",
|
"battery_fleet_all_hint": "Excluez un appareil ici avant même qu'il ne signale une charge faible — un aspirateur qui se recharge seul, ou un téléphone qui vous prévient lui-même.",
|
||||||
"battery_fleet_add": "Ajouter une batterie",
|
"battery_fleet_add": "Ajouter une batterie",
|
||||||
"battery_fleet_add_hint": "Choisissez un capteur de batterie que la détection automatique a manqué — il rejoint la liste immédiatement.",
|
"battery_fleet_add_hint": "Choisissez un capteur de batterie que la détection automatique a manqué — il rejoint la liste immédiatement.",
|
||||||
|
"battery_fleet_track_self": "Suivre les batteries à recharge automatique",
|
||||||
|
"battery_fleet_track_self_hint": "Les téléphones, aspirateurs et autres appareils qui se rechargent seuls apparaissent comme rechargeables — un niveau bas demande une recharge, jamais de nouvelles piles.",
|
||||||
"battery_fleet_status_low": "Faible",
|
"battery_fleet_status_low": "Faible",
|
||||||
"battery_fleet_status_soon": "Bientôt",
|
"battery_fleet_status_soon": "Bientôt",
|
||||||
"battery_fleet_status_ok": "Bon état",
|
"battery_fleet_status_ok": "Bon état",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "जल्द देय",
|
"due_soon": "जल्द देय",
|
||||||
"triggered": "ट्रिगर हुआ",
|
"triggered": "ट्रिगर हुआ",
|
||||||
"trigger_replaced": "ट्रिगर बदला गया",
|
"trigger_replaced": "ट्रिगर बदला गया",
|
||||||
|
"trigger_removed": "ट्रिगर हटाया गया",
|
||||||
"ok": "ठीक",
|
"ok": "ठीक",
|
||||||
"all": "सभी",
|
"all": "सभी",
|
||||||
"new_object": "+ नई वस्तु",
|
"new_object": "+ नई वस्तु",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "इसके अनुसार समूहित करें",
|
"group_by_label": "इसके अनुसार समूहित करें",
|
||||||
"state_value_help": "HA स्थिति मान का उपयोग करें (आमतौर पर लोअरकेस, उदा. \"on\"/\"off\")। सहेजने पर केस सामान्यीकृत हो जाता है।",
|
"state_value_help": "HA स्थिति मान का उपयोग करें (आमतौर पर लोअरकेस, उदा. \"on\"/\"off\")। सहेजने पर केस सामान्यीकृत हो जाता है।",
|
||||||
"target_changes_help": "ट्रिगर होने से पहले मिलान वाले संक्रमणों की संख्या (डिफ़ॉल्ट: 1)।",
|
"target_changes_help": "ट्रिगर होने से पहले मिलान वाले संक्रमणों की संख्या (डिफ़ॉल्ट: 1)।",
|
||||||
|
"for_minutes_state_help": "0 हर परिवर्तन को तुरंत गिनता है। मिनट सेट करने पर नई स्थिति को पहले उतनी देर बनी रहनी होगी — क्षणिक झिलमिलाहट तब न ट्रिगर करती है न गिनी जाती है।",
|
||||||
"qr_print_title": "QR कोड प्रिंट करें",
|
"qr_print_title": "QR कोड प्रिंट करें",
|
||||||
"qr_print_desc": "अपने उपकरण पर काटकर चिपकाने के लिए QR कोड का प्रिंट-योग्य पृष्ठ बनाएँ।",
|
"qr_print_desc": "अपने उपकरण पर काटकर चिपकाने के लिए QR कोड का प्रिंट-योग्य पृष्ठ बनाएँ।",
|
||||||
"qr_print_load": "वस्तुएँ लोड करें",
|
"qr_print_load": "वस्तुएँ लोड करें",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "समय पर पूर्णता मानते हुए।",
|
"schedule_preview_ontime": "समय पर पूर्णता मानते हुए।",
|
||||||
"schedule_preview_ends": "(श्रृंखला समाप्त)",
|
"schedule_preview_ends": "(श्रृंखला समाप्त)",
|
||||||
"adopt_problem_responsible": "सभी अपनाए गए कार्यों के लिए ज़िम्मेदार उपयोगकर्ता (वैकल्पिक)",
|
"adopt_problem_responsible": "सभी अपनाए गए कार्यों के लिए ज़िम्मेदार उपयोगकर्ता (वैकल्पिक)",
|
||||||
|
"adopt_for_minutes_hint": "समस्या इतनी देर बनी रहने पर ही ट्रिगर करें — 0 पहली झिलमिलाहट पर प्रतिक्रिया करता है।",
|
||||||
"adopt_problem_configure": "कॉन्फ़िगर करें",
|
"adopt_problem_configure": "कॉन्फ़िगर करें",
|
||||||
"history_auto": "स्वचालित",
|
"history_auto": "स्वचालित",
|
||||||
"battery_fleet_title": "बैटरी फ्लीट",
|
"battery_fleet_title": "बैटरी फ्लीट",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "किसी डिवाइस को यहाँ कम बैटरी बताने से पहले ही बाहर रखें — जैसे खुद चार्ज होने वाला वैक्यूम, या खुद ही चेतावनी देने वाला फ़ोन।",
|
"battery_fleet_all_hint": "किसी डिवाइस को यहाँ कम बैटरी बताने से पहले ही बाहर रखें — जैसे खुद चार्ज होने वाला वैक्यूम, या खुद ही चेतावनी देने वाला फ़ोन।",
|
||||||
"battery_fleet_add": "बैटरी जोड़ें",
|
"battery_fleet_add": "बैटरी जोड़ें",
|
||||||
"battery_fleet_add_hint": "स्वचालित खोज से छूटा बैटरी सेंसर चुनें — यह तुरंत सूची में जुड़ जाएगा।",
|
"battery_fleet_add_hint": "स्वचालित खोज से छूटा बैटरी सेंसर चुनें — यह तुरंत सूची में जुड़ जाएगा।",
|
||||||
|
"battery_fleet_track_self": "स्वयं चार्ज होने वाली बैटरियों को ट्रैक करें",
|
||||||
|
"battery_fleet_track_self_hint": "फ़ोन, वैक्यूम और अन्य स्वयं चार्ज होने वाले उपकरण रिचार्जेबल के रूप में दिखते हैं — कम होने पर चार्ज करने को कहा जाता है, नई बैटरी कभी नहीं।",
|
||||||
"battery_fleet_status_low": "कम",
|
"battery_fleet_status_low": "कम",
|
||||||
"battery_fleet_status_soon": "जल्द",
|
"battery_fleet_status_soon": "जल्द",
|
||||||
"battery_fleet_status_ok": "ठीक",
|
"battery_fleet_status_ok": "ठीक",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Hamarosan esedékes",
|
"due_soon": "Hamarosan esedékes",
|
||||||
"triggered": "Aktiválva",
|
"triggered": "Aktiválva",
|
||||||
"trigger_replaced": "Kiváltó lecserélve",
|
"trigger_replaced": "Kiváltó lecserélve",
|
||||||
|
"trigger_removed": "Eseményindító eltávolítva",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Összes",
|
"all": "Összes",
|
||||||
"new_object": "+ Új objektum",
|
"new_object": "+ Új objektum",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Csoportosítás",
|
"group_by_label": "Csoportosítás",
|
||||||
"state_value_help": "A HA állapotértékét használja (általában kisbetűs, pl. „on”/„off”). A kis- és nagybetűk mentéskor normalizálódnak.",
|
"state_value_help": "A HA állapotértékét használja (általában kisbetűs, pl. „on”/„off”). A kis- és nagybetűk mentéskor normalizálódnak.",
|
||||||
"target_changes_help": "Az egyező átmenetek száma a kiváltás előtt (alapértelmezés: 1).",
|
"target_changes_help": "Az egyező átmenetek száma a kiváltás előtt (alapértelmezés: 1).",
|
||||||
|
"for_minutes_state_help": "A 0 minden változást azonnal számol. Percekkel az új állapotnak előbb fenn kell maradnia — a rövid villanás ekkor se nem indít, se nem számít.",
|
||||||
"qr_print_title": "QR-kódok nyomtatása",
|
"qr_print_title": "QR-kódok nyomtatása",
|
||||||
"qr_print_desc": "Nyomtatható oldal QR-kódokkal — vágja ki és ragassza fel a berendezésekre.",
|
"qr_print_desc": "Nyomtatható oldal QR-kódokkal — vágja ki és ragassza fel a berendezésekre.",
|
||||||
"qr_print_load": "Objektumok betöltése",
|
"qr_print_load": "Objektumok betöltése",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Időben történő elvégzést feltételezve.",
|
"schedule_preview_ontime": "Időben történő elvégzést feltételezve.",
|
||||||
"schedule_preview_ends": "(a sorozat véget ér)",
|
"schedule_preview_ends": "(a sorozat véget ér)",
|
||||||
"adopt_problem_responsible": "Felelős felhasználó az összes átvett feladathoz (opcionális)",
|
"adopt_problem_responsible": "Felelős felhasználó az összes átvett feladathoz (opcionális)",
|
||||||
|
"adopt_for_minutes_hint": "Csak akkor induljon, ha a probléma ennyi ideig fennáll — a 0 az első villanásra reagál.",
|
||||||
"adopt_problem_configure": "Beállítás",
|
"adopt_problem_configure": "Beállítás",
|
||||||
"history_auto": "Automatikus",
|
"history_auto": "Automatikus",
|
||||||
"battery_fleet_title": "Elemflotta",
|
"battery_fleet_title": "Elemflotta",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Zárjon ki egy eszközt már azelőtt, hogy alacsony töltöttséget jelezne — például egy magát töltő porszívót vagy egy telefont, amely magától figyelmeztet.",
|
"battery_fleet_all_hint": "Zárjon ki egy eszközt már azelőtt, hogy alacsony töltöttséget jelezne — például egy magát töltő porszívót vagy egy telefont, amely magától figyelmeztet.",
|
||||||
"battery_fleet_add": "Elem hozzáadása",
|
"battery_fleet_add": "Elem hozzáadása",
|
||||||
"battery_fleet_add_hint": "Válassz egy elem-érzékelőt, amelyet az automatikus felismerés kihagyott — azonnal felkerül a listára.",
|
"battery_fleet_add_hint": "Válassz egy elem-érzékelőt, amelyet az automatikus felismerés kihagyott — azonnal felkerül a listára.",
|
||||||
|
"battery_fleet_track_self": "Öntöltő akkumulátorok követése",
|
||||||
|
"battery_fleet_track_self_hint": "A telefonok, porszívók és más öntöltő eszközök akkumulátorként jelennek meg — alacsony töltöttségnél töltést kérnek, sosem új elemet.",
|
||||||
"battery_fleet_status_low": "Alacsony",
|
"battery_fleet_status_low": "Alacsony",
|
||||||
"battery_fleet_status_soon": "Hamarosan",
|
"battery_fleet_status_soon": "Hamarosan",
|
||||||
"battery_fleet_status_ok": "Rendben",
|
"battery_fleet_status_ok": "Rendben",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Raggruppa per",
|
"group_by_label": "Raggruppa per",
|
||||||
"state_value_help": "Usa il valore di stato HA (di solito minuscolo, es. \"on\"/\"off\"). Il case viene normalizzato al salvataggio.",
|
"state_value_help": "Usa il valore di stato HA (di solito minuscolo, es. \"on\"/\"off\"). Il case viene normalizzato al salvataggio.",
|
||||||
"target_changes_help": "Numero di transizioni corrispondenti prima che il trigger si attivi (predefinito: 1).",
|
"target_changes_help": "Numero di transizioni corrispondenti prima che il trigger si attivi (predefinito: 1).",
|
||||||
|
"for_minutes_state_help": "0 conta ogni cambiamento subito. Con i minuti, il nuovo stato deve prima mantenersi — un breve sfarfallio allora non attiva né conta.",
|
||||||
"qr_print_title": "Stampa codici QR",
|
"qr_print_title": "Stampa codici QR",
|
||||||
"qr_print_desc": "Genera una pagina stampabile di codici QR da ritagliare e applicare sulle apparecchiature.",
|
"qr_print_desc": "Genera una pagina stampabile di codici QR da ritagliare e applicare sulle apparecchiature.",
|
||||||
"qr_print_load": "Carica oggetti",
|
"qr_print_load": "Carica oggetti",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Non necessario",
|
"quick_complete_defaults_feedback_not_needed": "Non necessario",
|
||||||
"quick_complete_success": "Completato rapidamente",
|
"quick_complete_success": "Completato rapidamente",
|
||||||
"trigger_replaced": "Trigger sostituito",
|
"trigger_replaced": "Trigger sostituito",
|
||||||
|
"trigger_removed": "Trigger rimosso",
|
||||||
"add": "Aggiungi",
|
"add": "Aggiungi",
|
||||||
"show_stats": "Mostra statistiche + grafici",
|
"show_stats": "Mostra statistiche + grafici",
|
||||||
"hide_stats": "Nascondi statistiche",
|
"hide_stats": "Nascondi statistiche",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Presumendo un completamento puntuale.",
|
"schedule_preview_ontime": "Presumendo un completamento puntuale.",
|
||||||
"schedule_preview_ends": "(la serie termina)",
|
"schedule_preview_ends": "(la serie termina)",
|
||||||
"adopt_problem_responsible": "Utente responsabile per tutte le attività adottate (opzionale)",
|
"adopt_problem_responsible": "Utente responsabile per tutte le attività adottate (opzionale)",
|
||||||
|
"adopt_for_minutes_hint": "Attiva solo se il problema persiste per questo tempo — 0 reagisce al primo sfarfallio.",
|
||||||
"adopt_problem_configure": "Configura",
|
"adopt_problem_configure": "Configura",
|
||||||
"history_auto": "Automatico",
|
"history_auto": "Automatico",
|
||||||
"battery_fleet_title": "Parco batterie",
|
"battery_fleet_title": "Parco batterie",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Escludi qui un dispositivo prima ancora che segnali la batteria scarica — un aspirapolvere che si ricarica da solo o un telefono che avvisa da sé.",
|
"battery_fleet_all_hint": "Escludi qui un dispositivo prima ancora che segnali la batteria scarica — un aspirapolvere che si ricarica da solo o un telefono che avvisa da sé.",
|
||||||
"battery_fleet_add": "Aggiungi una batteria",
|
"battery_fleet_add": "Aggiungi una batteria",
|
||||||
"battery_fleet_add_hint": "Scegli un sensore batteria sfuggito al rilevamento automatico — entra subito nell'elenco.",
|
"battery_fleet_add_hint": "Scegli un sensore batteria sfuggito al rilevamento automatico — entra subito nell'elenco.",
|
||||||
|
"battery_fleet_track_self": "Traccia le batterie autoricaricabili",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoni, aspirapolvere e altri dispositivi che si ricaricano da soli appaiono come ricaricabili — se scarichi chiedono una ricarica, mai batterie nuove.",
|
||||||
"battery_fleet_status_low": "Scarica",
|
"battery_fleet_status_low": "Scarica",
|
||||||
"battery_fleet_status_soon": "A breve",
|
"battery_fleet_status_soon": "A breve",
|
||||||
"battery_fleet_status_ok": "A posto",
|
"battery_fleet_status_ok": "A posto",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "期限間近",
|
"due_soon": "期限間近",
|
||||||
"triggered": "トリガー発生",
|
"triggered": "トリガー発生",
|
||||||
"trigger_replaced": "トリガー解除済み",
|
"trigger_replaced": "トリガー解除済み",
|
||||||
|
"trigger_removed": "トリガーを削除しました",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "すべて",
|
"all": "すべて",
|
||||||
"new_object": "+ 新規対象",
|
"new_object": "+ 新規対象",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "グループ化",
|
"group_by_label": "グループ化",
|
||||||
"state_value_help": "HAの状態値を使用してください(通常は小文字、例: \"on\"/\"off\")。保存時に大文字小文字は正規化されます。",
|
"state_value_help": "HAの状態値を使用してください(通常は小文字、例: \"on\"/\"off\")。保存時に大文字小文字は正規化されます。",
|
||||||
"target_changes_help": "トリガーが発生するまでに一致する遷移の回数(既定: 1)。",
|
"target_changes_help": "トリガーが発生するまでに一致する遷移の回数(既定: 1)。",
|
||||||
|
"for_minutes_state_help": "0 はすべての変化を即座にカウントします。分を設定すると新しい状態がその時間続く必要があり、一瞬の変動はトリガーもカウントもされません。",
|
||||||
"qr_print_title": "QRコードを印刷",
|
"qr_print_title": "QRコードを印刷",
|
||||||
"qr_print_desc": "切り取って機器に貼れるQRコードの印刷ページを生成します。",
|
"qr_print_desc": "切り取って機器に貼れるQRコードの印刷ページを生成します。",
|
||||||
"qr_print_load": "対象を読み込む",
|
"qr_print_load": "対象を読み込む",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "期日どおりに完了した場合。",
|
"schedule_preview_ontime": "期日どおりに完了した場合。",
|
||||||
"schedule_preview_ends": "(シリーズ終了)",
|
"schedule_preview_ends": "(シリーズ終了)",
|
||||||
"adopt_problem_responsible": "採用したすべてのタスクの担当ユーザー(任意)",
|
"adopt_problem_responsible": "採用したすべてのタスクの担当ユーザー(任意)",
|
||||||
|
"adopt_for_minutes_hint": "問題がこの時間続いた場合にのみトリガーします。0 は最初の変動に即反応します。",
|
||||||
"adopt_problem_configure": "設定",
|
"adopt_problem_configure": "設定",
|
||||||
"history_auto": "自動",
|
"history_auto": "自動",
|
||||||
"battery_fleet_title": "電池フリート",
|
"battery_fleet_title": "電池フリート",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "電池残量が少ないと報告される前に、ここで機器を除外できます — 自動充電するロボット掃除機や、自分で警告するスマートフォンなど。",
|
"battery_fleet_all_hint": "電池残量が少ないと報告される前に、ここで機器を除外できます — 自動充電するロボット掃除機や、自分で警告するスマートフォンなど。",
|
||||||
"battery_fleet_add": "電池を追加",
|
"battery_fleet_add": "電池を追加",
|
||||||
"battery_fleet_add_hint": "自動検出が見逃した電池センサーを選択すると、すぐに一覧に追加されます。",
|
"battery_fleet_add_hint": "自動検出が見逃した電池センサーを選択すると、すぐに一覧に追加されます。",
|
||||||
|
"battery_fleet_track_self": "自己充電式バッテリーを追跡",
|
||||||
|
"battery_fleet_track_self_hint": "スマートフォンや掃除機など自己充電するデバイスは充電式として表示されます。残量が少ないときは充電を促し、電池交換は求めません。",
|
||||||
"battery_fleet_status_low": "残量低下",
|
"battery_fleet_status_low": "残量低下",
|
||||||
"battery_fleet_status_soon": "まもなく",
|
"battery_fleet_status_soon": "まもなく",
|
||||||
"battery_fleet_status_ok": "良好",
|
"battery_fleet_status_ok": "良好",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "기한 임박",
|
"due_soon": "기한 임박",
|
||||||
"triggered": "트리거됨",
|
"triggered": "트리거됨",
|
||||||
"trigger_replaced": "트리거 교체됨",
|
"trigger_replaced": "트리거 교체됨",
|
||||||
|
"trigger_removed": "트리거 제거됨",
|
||||||
"ok": "정상",
|
"ok": "정상",
|
||||||
"all": "전체",
|
"all": "전체",
|
||||||
"new_object": "+ 새 객체",
|
"new_object": "+ 새 객체",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "그룹화",
|
"group_by_label": "그룹화",
|
||||||
"state_value_help": "HA 상태 값을 사용하세요(보통 소문자, 예: \"on\"/\"off\"). 대소문자는 저장 시 정규화됩니다.",
|
"state_value_help": "HA 상태 값을 사용하세요(보통 소문자, 예: \"on\"/\"off\"). 대소문자는 저장 시 정규화됩니다.",
|
||||||
"target_changes_help": "트리거가 발동하기까지 필요한 일치 전환 횟수입니다 (기본값: 1).",
|
"target_changes_help": "트리거가 발동하기까지 필요한 일치 전환 횟수입니다 (기본값: 1).",
|
||||||
|
"for_minutes_state_help": "0은 모든 변화를 즉시 집계합니다. 분을 설정하면 새 상태가 먼저 그 시간 동안 유지되어야 하며, 짧은 깜빡임은 트리거되지도 집계되지도 않습니다.",
|
||||||
"qr_print_title": "QR 코드 인쇄",
|
"qr_print_title": "QR 코드 인쇄",
|
||||||
"qr_print_desc": "잘라서 장비에 붙일 수 있는 QR 코드 인쇄 페이지를 생성합니다.",
|
"qr_print_desc": "잘라서 장비에 붙일 수 있는 QR 코드 인쇄 페이지를 생성합니다.",
|
||||||
"qr_print_load": "객체 불러오기",
|
"qr_print_load": "객체 불러오기",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "제때 완료한다고 가정한 일정입니다.",
|
"schedule_preview_ontime": "제때 완료한다고 가정한 일정입니다.",
|
||||||
"schedule_preview_ends": "(시리즈 종료)",
|
"schedule_preview_ends": "(시리즈 종료)",
|
||||||
"adopt_problem_responsible": "가져온 모든 작업의 담당자 (선택)",
|
"adopt_problem_responsible": "가져온 모든 작업의 담당자 (선택)",
|
||||||
|
"adopt_for_minutes_hint": "문제가 이 시간 동안 지속된 경우에만 트리거합니다. 0은 첫 깜빡임에 즉시 반응합니다.",
|
||||||
"adopt_problem_configure": "구성",
|
"adopt_problem_configure": "구성",
|
||||||
"history_auto": "자동",
|
"history_auto": "자동",
|
||||||
"battery_fleet_title": "배터리 플릿",
|
"battery_fleet_title": "배터리 플릿",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "배터리 부족을 알리기 전에 여기에서 기기를 제외하세요 — 스스로 충전하는 로봇청소기나 자체적으로 경고하는 휴대폰처럼.",
|
"battery_fleet_all_hint": "배터리 부족을 알리기 전에 여기에서 기기를 제외하세요 — 스스로 충전하는 로봇청소기나 자체적으로 경고하는 휴대폰처럼.",
|
||||||
"battery_fleet_add": "배터리 추가",
|
"battery_fleet_add": "배터리 추가",
|
||||||
"battery_fleet_add_hint": "자동 검색이 놓친 배터리 센서를 선택하면 즉시 목록에 추가됩니다.",
|
"battery_fleet_add_hint": "자동 검색이 놓친 배터리 센서를 선택하면 즉시 목록에 추가됩니다.",
|
||||||
|
"battery_fleet_track_self": "자체 충전 배터리 추적",
|
||||||
|
"battery_fleet_track_self_hint": "휴대폰, 청소기 등 스스로 충전되는 기기는 충전식으로 표시됩니다. 잔량이 낮으면 충전을 요청하며 새 전지를 요구하지 않습니다.",
|
||||||
"battery_fleet_status_low": "부족",
|
"battery_fleet_status_low": "부족",
|
||||||
"battery_fleet_status_soon": "곧",
|
"battery_fleet_status_soon": "곧",
|
||||||
"battery_fleet_status_ok": "정상",
|
"battery_fleet_status_ok": "정상",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Forfaller snart",
|
"due_soon": "Forfaller snart",
|
||||||
"triggered": "Utløst",
|
"triggered": "Utløst",
|
||||||
"trigger_replaced": "Utløser erstattet",
|
"trigger_replaced": "Utløser erstattet",
|
||||||
|
"trigger_removed": "Utløser fjernet",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"new_object": "+ Nytt objekt",
|
"new_object": "+ Nytt objekt",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Grupper etter",
|
"group_by_label": "Grupper etter",
|
||||||
"state_value_help": "Bruk HA-tilstandsverdien (vanligvis små bokstaver, f.eks. \"on\"/\"off\"). Store/små bokstaver normaliseres ved lagring.",
|
"state_value_help": "Bruk HA-tilstandsverdien (vanligvis små bokstaver, f.eks. \"on\"/\"off\"). Store/små bokstaver normaliseres ved lagring.",
|
||||||
"target_changes_help": "Antall samsvarende overganger før utløseren aktiveres (standard: 1).",
|
"target_changes_help": "Antall samsvarende overganger før utløseren aktiveres (standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 teller hver endring umiddelbart. Med minutter må den nye tilstanden først holde seg — korte blink verken utløser eller telles da.",
|
||||||
"qr_print_title": "Skriv ut QR-koder",
|
"qr_print_title": "Skriv ut QR-koder",
|
||||||
"qr_print_desc": "Generer en utskrivbar side med QR-koder som kan klippes ut og festes på utstyret ditt.",
|
"qr_print_desc": "Generer en utskrivbar side med QR-koder som kan klippes ut og festes på utstyret ditt.",
|
||||||
"qr_print_load": "Last objekter",
|
"qr_print_load": "Last objekter",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Forutsatt fullføring i tide.",
|
"schedule_preview_ontime": "Forutsatt fullføring i tide.",
|
||||||
"schedule_preview_ends": "(serien avsluttes)",
|
"schedule_preview_ends": "(serien avsluttes)",
|
||||||
"adopt_problem_responsible": "Ansvarlig bruker for alle overtatte oppgaver (valgfritt)",
|
"adopt_problem_responsible": "Ansvarlig bruker for alle overtatte oppgaver (valgfritt)",
|
||||||
|
"adopt_for_minutes_hint": "Utløs først når problemet har vart så lenge — 0 reagerer på første blink.",
|
||||||
"adopt_problem_configure": "Konfigurer",
|
"adopt_problem_configure": "Konfigurer",
|
||||||
"history_auto": "Automatisk",
|
"history_auto": "Automatisk",
|
||||||
"battery_fleet_title": "Batteriflåte",
|
"battery_fleet_title": "Batteriflåte",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Utelukk en enhet her før den i det hele tatt melder lavt nivå — en støvsuger som lader seg selv, eller en telefon som varsler av seg selv.",
|
"battery_fleet_all_hint": "Utelukk en enhet her før den i det hele tatt melder lavt nivå — en støvsuger som lader seg selv, eller en telefon som varsler av seg selv.",
|
||||||
"battery_fleet_add": "Legg til et batteri",
|
"battery_fleet_add": "Legg til et batteri",
|
||||||
"battery_fleet_add_hint": "Velg en batterisensor som den automatiske gjenkjenningen overså — den blir med i listen umiddelbart.",
|
"battery_fleet_add_hint": "Velg en batterisensor som den automatiske gjenkjenningen overså — den blir med i listen umiddelbart.",
|
||||||
|
"battery_fleet_track_self": "Spor selvladende batterier",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoner, støvsugere og andre selvladende enheter vises som oppladbare — ved lavt nivå bes det om lading, aldri om nye batterier.",
|
||||||
"battery_fleet_status_low": "Lav",
|
"battery_fleet_status_low": "Lav",
|
||||||
"battery_fleet_status_soon": "Snart",
|
"battery_fleet_status_soon": "Snart",
|
||||||
"battery_fleet_status_ok": "I orden",
|
"battery_fleet_status_ok": "I orden",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Groeperen op",
|
"group_by_label": "Groeperen op",
|
||||||
"state_value_help": "Gebruik de HA-statuswaarde (meestal in kleine letters, bv. \"on\"/\"off\"). Hoofdletters worden bij opslaan genormaliseerd.",
|
"state_value_help": "Gebruik de HA-statuswaarde (meestal in kleine letters, bv. \"on\"/\"off\"). Hoofdletters worden bij opslaan genormaliseerd.",
|
||||||
"target_changes_help": "Aantal overeenkomende overgangen voordat de trigger wordt geactiveerd (standaard: 1).",
|
"target_changes_help": "Aantal overeenkomende overgangen voordat de trigger wordt geactiveerd (standaard: 1).",
|
||||||
|
"for_minutes_state_help": "0 telt elke wijziging direct. Met minuten moet de nieuwe status eerst zo lang aanhouden — kort flikkeren triggert dan niet en telt niet mee.",
|
||||||
"qr_print_title": "QR-codes afdrukken",
|
"qr_print_title": "QR-codes afdrukken",
|
||||||
"qr_print_desc": "Genereer een afdrukpagina met QR-codes om uit te knippen en op je apparaten te plakken.",
|
"qr_print_desc": "Genereer een afdrukpagina met QR-codes om uit te knippen en op je apparaten te plakken.",
|
||||||
"qr_print_load": "Objecten laden",
|
"qr_print_load": "Objecten laden",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Niet nodig",
|
"quick_complete_defaults_feedback_not_needed": "Niet nodig",
|
||||||
"quick_complete_success": "Snel als voltooid gemarkeerd",
|
"quick_complete_success": "Snel als voltooid gemarkeerd",
|
||||||
"trigger_replaced": "Trigger vervangen",
|
"trigger_replaced": "Trigger vervangen",
|
||||||
|
"trigger_removed": "Trigger verwijderd",
|
||||||
"add": "Toevoegen",
|
"add": "Toevoegen",
|
||||||
"show_stats": "Statistieken + grafieken tonen",
|
"show_stats": "Statistieken + grafieken tonen",
|
||||||
"hide_stats": "Statistieken verbergen",
|
"hide_stats": "Statistieken verbergen",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Uitgaande van tijdige afronding.",
|
"schedule_preview_ontime": "Uitgaande van tijdige afronding.",
|
||||||
"schedule_preview_ends": "(reeks eindigt)",
|
"schedule_preview_ends": "(reeks eindigt)",
|
||||||
"adopt_problem_responsible": "Verantwoordelijke gebruiker voor alle overgenomen taken (optioneel)",
|
"adopt_problem_responsible": "Verantwoordelijke gebruiker voor alle overgenomen taken (optioneel)",
|
||||||
|
"adopt_for_minutes_hint": "Pas triggeren als het probleem zo lang aanhoudt — 0 reageert op de eerste flikkering.",
|
||||||
"adopt_problem_configure": "Configureren",
|
"adopt_problem_configure": "Configureren",
|
||||||
"history_auto": "Automatisch",
|
"history_auto": "Automatisch",
|
||||||
"battery_fleet_title": "Batterijvloot",
|
"battery_fleet_title": "Batterijvloot",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Sluit een apparaat hier uit voordat het ooit laag meldt — een stofzuiger die zichzelf oplaadt, of een telefoon die zelf waarschuwt.",
|
"battery_fleet_all_hint": "Sluit een apparaat hier uit voordat het ooit laag meldt — een stofzuiger die zichzelf oplaadt, of een telefoon die zelf waarschuwt.",
|
||||||
"battery_fleet_add": "Batterij toevoegen",
|
"battery_fleet_add": "Batterij toevoegen",
|
||||||
"battery_fleet_add_hint": "Kies een batterijsensor die de automatische detectie heeft gemist — hij verschijnt direct in de lijst.",
|
"battery_fleet_add_hint": "Kies een batterijsensor die de automatische detectie heeft gemist — hij verschijnt direct in de lijst.",
|
||||||
|
"battery_fleet_track_self": "Zelfopladende batterijen volgen",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoons, stofzuigers en andere zelfopladende apparaten verschijnen als oplaadbaar — bij een lage stand wordt om opladen gevraagd, nooit om nieuwe batterijen.",
|
||||||
"battery_fleet_status_low": "Laag",
|
"battery_fleet_status_low": "Laag",
|
||||||
"battery_fleet_status_soon": "Binnenkort",
|
"battery_fleet_status_soon": "Binnenkort",
|
||||||
"battery_fleet_status_ok": "In orde",
|
"battery_fleet_status_ok": "In orde",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Grupuj wg",
|
"group_by_label": "Grupuj wg",
|
||||||
"state_value_help": "Użyj wartości stanu HA (zwykle małymi literami, np. \"on\"/\"off\"). Wielkość liter jest normalizowana przy zapisie.",
|
"state_value_help": "Użyj wartości stanu HA (zwykle małymi literami, np. \"on\"/\"off\"). Wielkość liter jest normalizowana przy zapisie.",
|
||||||
"target_changes_help": "Liczba pasujących przejść przed wyzwoleniem (domyślnie: 1).",
|
"target_changes_help": "Liczba pasujących przejść przed wyzwoleniem (domyślnie: 1).",
|
||||||
|
"for_minutes_state_help": "0 liczy każdą zmianę od razu. Z minutami nowy stan musi się najpierw utrzymać — krótkie mignięcia wtedy nie wyzwalają i nie liczą się.",
|
||||||
"qr_print_title": "Drukuj kody QR",
|
"qr_print_title": "Drukuj kody QR",
|
||||||
"qr_print_desc": "Wygeneruj stronę do druku z kodami QR do wycięcia i naklejenia na sprzęcie.",
|
"qr_print_desc": "Wygeneruj stronę do druku z kodami QR do wycięcia i naklejenia na sprzęcie.",
|
||||||
"qr_print_load": "Załaduj obiekty",
|
"qr_print_load": "Załaduj obiekty",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Nie było potrzebne",
|
"quick_complete_defaults_feedback_not_needed": "Nie było potrzebne",
|
||||||
"quick_complete_success": "Szybko oznaczono jako wykonane",
|
"quick_complete_success": "Szybko oznaczono jako wykonane",
|
||||||
"trigger_replaced": "Wyzwalacz zastąpiony",
|
"trigger_replaced": "Wyzwalacz zastąpiony",
|
||||||
|
"trigger_removed": "Wyzwalacz usunięty",
|
||||||
"add": "Dodaj",
|
"add": "Dodaj",
|
||||||
"show_stats": "Pokaż statystyki + wykresy",
|
"show_stats": "Pokaż statystyki + wykresy",
|
||||||
"hide_stats": "Ukryj statystyki",
|
"hide_stats": "Ukryj statystyki",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Przy założeniu terminowego ukończenia.",
|
"schedule_preview_ontime": "Przy założeniu terminowego ukończenia.",
|
||||||
"schedule_preview_ends": "(seria się kończy)",
|
"schedule_preview_ends": "(seria się kończy)",
|
||||||
"adopt_problem_responsible": "Osoba odpowiedzialna za wszystkie przejęte zadania (opcjonalnie)",
|
"adopt_problem_responsible": "Osoba odpowiedzialna za wszystkie przejęte zadania (opcjonalnie)",
|
||||||
|
"adopt_for_minutes_hint": "Wyzwalaj dopiero, gdy problem utrzymuje się tak długo — 0 reaguje na pierwsze mignięcie.",
|
||||||
"adopt_problem_configure": "Konfiguruj",
|
"adopt_problem_configure": "Konfiguruj",
|
||||||
"history_auto": "Automatycznie",
|
"history_auto": "Automatycznie",
|
||||||
"battery_fleet_title": "Flota baterii",
|
"battery_fleet_title": "Flota baterii",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Wyklucz tutaj urządzenie, zanim w ogóle zgłosi niski poziom — odkurzacz, który sam się ładuje, albo telefon, który ostrzega samodzielnie.",
|
"battery_fleet_all_hint": "Wyklucz tutaj urządzenie, zanim w ogóle zgłosi niski poziom — odkurzacz, który sam się ładuje, albo telefon, który ostrzega samodzielnie.",
|
||||||
"battery_fleet_add": "Dodaj baterię",
|
"battery_fleet_add": "Dodaj baterię",
|
||||||
"battery_fleet_add_hint": "Wybierz czujnik baterii pominięty przez automatyczne wykrywanie — od razu trafi na listę.",
|
"battery_fleet_add_hint": "Wybierz czujnik baterii pominięty przez automatyczne wykrywanie — od razu trafi na listę.",
|
||||||
|
"battery_fleet_track_self": "Śledź baterie samoładujące",
|
||||||
|
"battery_fleet_track_self_hint": "Telefony, odkurzacze i inne samoładujące się urządzenia pojawiają się jako akumulatory — przy niskim poziomie proszą o ładowanie, nigdy o nowe baterie.",
|
||||||
"battery_fleet_status_low": "Niski",
|
"battery_fleet_status_low": "Niski",
|
||||||
"battery_fleet_status_soon": "Wkrótce",
|
"battery_fleet_status_soon": "Wkrótce",
|
||||||
"battery_fleet_status_ok": "W porządku",
|
"battery_fleet_status_ok": "W porządku",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Vence em breve",
|
"due_soon": "Vence em breve",
|
||||||
"triggered": "Acionada",
|
"triggered": "Acionada",
|
||||||
"trigger_replaced": "Gatilho substituído",
|
"trigger_replaced": "Gatilho substituído",
|
||||||
|
"trigger_removed": "Gatilho removido",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Todas",
|
"all": "Todas",
|
||||||
"new_object": "+ Novo objeto",
|
"new_object": "+ Novo objeto",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Agrupar por",
|
"group_by_label": "Agrupar por",
|
||||||
"state_value_help": "Use o valor de estado do HA (geralmente minúsculo, ex.: \"on\"/\"off\"). Maiúsculas e minúsculas são normalizadas ao salvar.",
|
"state_value_help": "Use o valor de estado do HA (geralmente minúsculo, ex.: \"on\"/\"off\"). Maiúsculas e minúsculas são normalizadas ao salvar.",
|
||||||
"target_changes_help": "Número de transições correspondentes antes de o gatilho disparar (padrão: 1).",
|
"target_changes_help": "Número de transições correspondentes antes de o gatilho disparar (padrão: 1).",
|
||||||
|
"for_minutes_state_help": "0 conta cada mudança imediatamente. Com minutos, o novo estado deve se manter primeiro — uma oscilação breve então não dispara nem conta.",
|
||||||
"qr_print_title": "Imprimir códigos QR",
|
"qr_print_title": "Imprimir códigos QR",
|
||||||
"qr_print_desc": "Gere uma página imprimível de códigos QR para recortar e colar nos seus equipamentos.",
|
"qr_print_desc": "Gere uma página imprimível de códigos QR para recortar e colar nos seus equipamentos.",
|
||||||
"qr_print_load": "Carregar objetos",
|
"qr_print_load": "Carregar objetos",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Supondo conclusão em dia.",
|
"schedule_preview_ontime": "Supondo conclusão em dia.",
|
||||||
"schedule_preview_ends": "(a série termina)",
|
"schedule_preview_ends": "(a série termina)",
|
||||||
"adopt_problem_responsible": "Usuário responsável por todas as tarefas adotadas (opcional)",
|
"adopt_problem_responsible": "Usuário responsável por todas as tarefas adotadas (opcional)",
|
||||||
|
"adopt_for_minutes_hint": "Disparar apenas se o problema persistir por esse tempo — 0 reage à primeira oscilação.",
|
||||||
"adopt_problem_configure": "Configurar",
|
"adopt_problem_configure": "Configurar",
|
||||||
"history_auto": "Automática",
|
"history_auto": "Automática",
|
||||||
"battery_fleet_title": "Frota de baterias",
|
"battery_fleet_title": "Frota de baterias",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes que ele sequer indique carga baixa — um aspirador que se recarrega sozinho ou um celular que já avisa sozinho.",
|
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes que ele sequer indique carga baixa — um aspirador que se recarrega sozinho ou um celular que já avisa sozinho.",
|
||||||
"battery_fleet_add": "Adicionar uma bateria",
|
"battery_fleet_add": "Adicionar uma bateria",
|
||||||
"battery_fleet_add_hint": "Escolha um sensor de bateria que a detecção automática não encontrou — ele entra na lista imediatamente.",
|
"battery_fleet_add_hint": "Escolha um sensor de bateria que a detecção automática não encontrou — ele entra na lista imediatamente.",
|
||||||
|
"battery_fleet_track_self": "Acompanhar baterias de carregamento automático",
|
||||||
|
"battery_fleet_track_self_hint": "Telefones, aspiradores e outros dispositivos que se recarregam sozinhos aparecem como recarregáveis — com carga baixa pedem uma recarga, nunca pilhas novas.",
|
||||||
"battery_fleet_status_low": "Fraca",
|
"battery_fleet_status_low": "Fraca",
|
||||||
"battery_fleet_status_soon": "Em breve",
|
"battery_fleet_status_soon": "Em breve",
|
||||||
"battery_fleet_status_ok": "Em bom estado",
|
"battery_fleet_status_ok": "Em bom estado",
|
||||||
|
|||||||
@@ -513,6 +513,7 @@
|
|||||||
"group_by_label": "Agrupar por",
|
"group_by_label": "Agrupar por",
|
||||||
"state_value_help": "Use o valor de estado HA (normalmente em minúsculas, p. ex. \"on\"/\"off\"). As maiúsculas são normalizadas ao guardar.",
|
"state_value_help": "Use o valor de estado HA (normalmente em minúsculas, p. ex. \"on\"/\"off\"). As maiúsculas são normalizadas ao guardar.",
|
||||||
"target_changes_help": "Número de transições correspondentes antes do trigger disparar (predefinido: 1).",
|
"target_changes_help": "Número de transições correspondentes antes do trigger disparar (predefinido: 1).",
|
||||||
|
"for_minutes_state_help": "0 conta cada mudança de imediato. Com minutos, o novo estado tem de se manter primeiro — uma oscilação breve então não dispara nem conta.",
|
||||||
"qr_print_title": "Imprimir códigos QR",
|
"qr_print_title": "Imprimir códigos QR",
|
||||||
"qr_print_desc": "Gera uma página imprimível de códigos QR para recortar e colar nos equipamentos.",
|
"qr_print_desc": "Gera uma página imprimível de códigos QR para recortar e colar nos equipamentos.",
|
||||||
"qr_print_load": "Carregar objetos",
|
"qr_print_load": "Carregar objetos",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Não era necessário",
|
"quick_complete_defaults_feedback_not_needed": "Não era necessário",
|
||||||
"quick_complete_success": "Concluído rapidamente",
|
"quick_complete_success": "Concluído rapidamente",
|
||||||
"trigger_replaced": "Acionador substituído",
|
"trigger_replaced": "Acionador substituído",
|
||||||
|
"trigger_removed": "Gatilho removido",
|
||||||
"add": "Adicionar",
|
"add": "Adicionar",
|
||||||
"show_stats": "Mostrar estatísticas + gráficos",
|
"show_stats": "Mostrar estatísticas + gráficos",
|
||||||
"hide_stats": "Ocultar estatísticas",
|
"hide_stats": "Ocultar estatísticas",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Assumindo conclusão pontual.",
|
"schedule_preview_ontime": "Assumindo conclusão pontual.",
|
||||||
"schedule_preview_ends": "(a série termina)",
|
"schedule_preview_ends": "(a série termina)",
|
||||||
"adopt_problem_responsible": "Utilizador responsável por todas as tarefas adotadas (opcional)",
|
"adopt_problem_responsible": "Utilizador responsável por todas as tarefas adotadas (opcional)",
|
||||||
|
"adopt_for_minutes_hint": "Disparar apenas se o problema persistir este tempo — 0 reage à primeira oscilação.",
|
||||||
"adopt_problem_configure": "Configurar",
|
"adopt_problem_configure": "Configurar",
|
||||||
"history_auto": "Automático",
|
"history_auto": "Automático",
|
||||||
"battery_fleet_title": "Frota de baterias",
|
"battery_fleet_title": "Frota de baterias",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes de ele sequer indicar carga fraca — um aspirador que se recarrega sozinho ou um telemóvel que já avisa por si.",
|
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes de ele sequer indicar carga fraca — um aspirador que se recarrega sozinho ou um telemóvel que já avisa por si.",
|
||||||
"battery_fleet_add": "Adicionar uma bateria",
|
"battery_fleet_add": "Adicionar uma bateria",
|
||||||
"battery_fleet_add_hint": "Escolha um sensor de bateria que a deteção automática não encontrou — entra de imediato na lista.",
|
"battery_fleet_add_hint": "Escolha um sensor de bateria que a deteção automática não encontrou — entra de imediato na lista.",
|
||||||
|
"battery_fleet_track_self": "Acompanhar baterias de carregamento automático",
|
||||||
|
"battery_fleet_track_self_hint": "Telefones, aspiradores e outros dispositivos que se recarregam sozinhos aparecem como recarregáveis — com carga baixa pedem um carregamento, nunca pilhas novas.",
|
||||||
"battery_fleet_status_low": "Fraca",
|
"battery_fleet_status_low": "Fraca",
|
||||||
"battery_fleet_status_soon": "Em breve",
|
"battery_fleet_status_soon": "Em breve",
|
||||||
"battery_fleet_status_ok": "Em bom estado",
|
"battery_fleet_status_ok": "Em bom estado",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Группировать по",
|
"group_by_label": "Группировать по",
|
||||||
"state_value_help": "Используйте значение состояния HA (обычно в нижнем регистре, напр. \"on\"/\"off\"). Регистр нормализуется при сохранении.",
|
"state_value_help": "Используйте значение состояния HA (обычно в нижнем регистре, напр. \"on\"/\"off\"). Регистр нормализуется при сохранении.",
|
||||||
"target_changes_help": "Количество совпадающих переходов, после которых срабатывает триггер (по умолчанию: 1).",
|
"target_changes_help": "Количество совпадающих переходов, после которых срабатывает триггер (по умолчанию: 1).",
|
||||||
|
"for_minutes_state_help": "0 — каждое изменение засчитывается сразу. С минутами новое состояние сначала должно удержаться — короткие сбои тогда не срабатывают и не считаются.",
|
||||||
"qr_print_title": "Печать QR-кодов",
|
"qr_print_title": "Печать QR-кодов",
|
||||||
"qr_print_desc": "Создай страницу для печати с QR-кодами для вырезания и наклеивания на оборудование.",
|
"qr_print_desc": "Создай страницу для печати с QR-кодами для вырезания и наклеивания на оборудование.",
|
||||||
"qr_print_load": "Загрузить объекты",
|
"qr_print_load": "Загрузить объекты",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Не было необходимо",
|
"quick_complete_defaults_feedback_not_needed": "Не было необходимо",
|
||||||
"quick_complete_success": "Быстро отмечено как выполнено",
|
"quick_complete_success": "Быстро отмечено как выполнено",
|
||||||
"trigger_replaced": "Триггер заменён",
|
"trigger_replaced": "Триггер заменён",
|
||||||
|
"trigger_removed": "Триггер удалён",
|
||||||
"add": "Добавить",
|
"add": "Добавить",
|
||||||
"show_stats": "Показать статистику + графики",
|
"show_stats": "Показать статистику + графики",
|
||||||
"hide_stats": "Скрыть статистику",
|
"hide_stats": "Скрыть статистику",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "При условии своевременного выполнения.",
|
"schedule_preview_ontime": "При условии своевременного выполнения.",
|
||||||
"schedule_preview_ends": "(серия завершается)",
|
"schedule_preview_ends": "(серия завершается)",
|
||||||
"adopt_problem_responsible": "Ответственный пользователь для всех принятых задач (необязательно)",
|
"adopt_problem_responsible": "Ответственный пользователь для всех принятых задач (необязательно)",
|
||||||
|
"adopt_for_minutes_hint": "Срабатывать только если проблема сохраняется это время — 0 реагирует на первый сбой.",
|
||||||
"adopt_problem_configure": "Настроить",
|
"adopt_problem_configure": "Настроить",
|
||||||
"history_auto": "Автоматически",
|
"history_auto": "Автоматически",
|
||||||
"battery_fleet_title": "Парк батарей",
|
"battery_fleet_title": "Парк батарей",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Исключите устройство здесь ещё до того, как оно сообщит о низком заряде — робот-пылесос, который заряжается сам, или телефон, который предупреждает вас сам.",
|
"battery_fleet_all_hint": "Исключите устройство здесь ещё до того, как оно сообщит о низком заряде — робот-пылесос, который заряжается сам, или телефон, который предупреждает вас сам.",
|
||||||
"battery_fleet_add": "Добавить батарею",
|
"battery_fleet_add": "Добавить батарею",
|
||||||
"battery_fleet_add_hint": "Выберите датчик батареи, который автоматическое обнаружение пропустило — он сразу появится в списке.",
|
"battery_fleet_add_hint": "Выберите датчик батареи, который автоматическое обнаружение пропустило — он сразу появится в списке.",
|
||||||
|
"battery_fleet_track_self": "Отслеживать самозаряжающиеся батареи",
|
||||||
|
"battery_fleet_track_self_hint": "Телефоны, пылесосы и другие самозаряжающиеся устройства отображаются как аккумуляторы — при низком заряде нужна зарядка, а не новые батарейки.",
|
||||||
"battery_fleet_status_low": "Низкий",
|
"battery_fleet_status_low": "Низкий",
|
||||||
"battery_fleet_status_soon": "Скоро",
|
"battery_fleet_status_soon": "Скоро",
|
||||||
"battery_fleet_status_ok": "В норме",
|
"battery_fleet_status_ok": "В норме",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Gruppera efter",
|
"group_by_label": "Gruppera efter",
|
||||||
"state_value_help": "Använd HA-tillståndsvärdet (vanligtvis med små bokstäver, t.ex. \"on\"/\"off\"). Versaler normaliseras vid sparande.",
|
"state_value_help": "Använd HA-tillståndsvärdet (vanligtvis med små bokstäver, t.ex. \"on\"/\"off\"). Versaler normaliseras vid sparande.",
|
||||||
"target_changes_help": "Antal matchande övergångar innan utlösaren aktiveras (standard: 1).",
|
"target_changes_help": "Antal matchande övergångar innan utlösaren aktiveras (standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 räknar varje ändring direkt. Med minuter måste det nya tillståndet först hålla i sig — korta blinkningar varken utlöser eller räknas då.",
|
||||||
"qr_print_title": "Skriv ut QR-koder",
|
"qr_print_title": "Skriv ut QR-koder",
|
||||||
"qr_print_desc": "Skapa en utskriftssida med QR-koder att klippa ut och sätta på din utrustning.",
|
"qr_print_desc": "Skapa en utskriftssida med QR-koder att klippa ut och sätta på din utrustning.",
|
||||||
"qr_print_load": "Ladda objekt",
|
"qr_print_load": "Ladda objekt",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Inte nödvändigt",
|
"quick_complete_defaults_feedback_not_needed": "Inte nödvändigt",
|
||||||
"quick_complete_success": "Snabbt markerad som klar",
|
"quick_complete_success": "Snabbt markerad som klar",
|
||||||
"trigger_replaced": "Utlösare ersatt",
|
"trigger_replaced": "Utlösare ersatt",
|
||||||
|
"trigger_removed": "Utlösare borttagen",
|
||||||
"add": "Lägg till",
|
"add": "Lägg till",
|
||||||
"show_stats": "Visa statistik + grafer",
|
"show_stats": "Visa statistik + grafer",
|
||||||
"hide_stats": "Dölj statistik",
|
"hide_stats": "Dölj statistik",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Förutsatt slutförande i tid.",
|
"schedule_preview_ontime": "Förutsatt slutförande i tid.",
|
||||||
"schedule_preview_ends": "(serien avslutas)",
|
"schedule_preview_ends": "(serien avslutas)",
|
||||||
"adopt_problem_responsible": "Ansvarig användare för alla övertagna uppgifter (valfritt)",
|
"adopt_problem_responsible": "Ansvarig användare för alla övertagna uppgifter (valfritt)",
|
||||||
|
"adopt_for_minutes_hint": "Utlös först när problemet kvarstått så här länge — 0 reagerar på första blinkningen.",
|
||||||
"adopt_problem_configure": "Konfigurera",
|
"adopt_problem_configure": "Konfigurera",
|
||||||
"history_auto": "Automatiskt",
|
"history_auto": "Automatiskt",
|
||||||
"battery_fleet_title": "Batteriflotta",
|
"battery_fleet_title": "Batteriflotta",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Uteslut en enhet här redan innan den rapporterar låg nivå — en dammsugare som laddar sig själv, eller en telefon som varnar på egen hand.",
|
"battery_fleet_all_hint": "Uteslut en enhet här redan innan den rapporterar låg nivå — en dammsugare som laddar sig själv, eller en telefon som varnar på egen hand.",
|
||||||
"battery_fleet_add": "Lägg till ett batteri",
|
"battery_fleet_add": "Lägg till ett batteri",
|
||||||
"battery_fleet_add_hint": "Välj en batterisensor som den automatiska identifieringen missade — den läggs till i listan direkt.",
|
"battery_fleet_add_hint": "Välj en batterisensor som den automatiska identifieringen missade — den läggs till i listan direkt.",
|
||||||
|
"battery_fleet_track_self": "Spåra självladdande batterier",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoner, dammsugare och andra självladdande enheter visas som laddningsbara — vid låg nivå begärs laddning, aldrig nya batterier.",
|
||||||
"battery_fleet_status_low": "Låg",
|
"battery_fleet_status_low": "Låg",
|
||||||
"battery_fleet_status_soon": "Snart",
|
"battery_fleet_status_soon": "Snart",
|
||||||
"battery_fleet_status_ok": "I ordning",
|
"battery_fleet_status_ok": "I ordning",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Yaklaşan",
|
"due_soon": "Yaklaşan",
|
||||||
"triggered": "Tetiklendi",
|
"triggered": "Tetiklendi",
|
||||||
"trigger_replaced": "Tetikleyici değiştirildi",
|
"trigger_replaced": "Tetikleyici değiştirildi",
|
||||||
|
"trigger_removed": "Tetikleyici kaldırıldı",
|
||||||
"ok": "Tamam",
|
"ok": "Tamam",
|
||||||
"all": "Tümü",
|
"all": "Tümü",
|
||||||
"new_object": "+ Yeni Nesne",
|
"new_object": "+ Yeni Nesne",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Grupla",
|
"group_by_label": "Grupla",
|
||||||
"state_value_help": "HA durum değerini kullanın (genellikle küçük harf, örn. \"on\"/\"off\"). Büyük/küçük harf kaydederken normalleştirilir.",
|
"state_value_help": "HA durum değerini kullanın (genellikle küçük harf, örn. \"on\"/\"off\"). Büyük/küçük harf kaydederken normalleştirilir.",
|
||||||
"target_changes_help": "Tetikleyici çalışmadan önceki eşleşen geçiş sayısı (varsayılan: 1).",
|
"target_changes_help": "Tetikleyici çalışmadan önceki eşleşen geçiş sayısı (varsayılan: 1).",
|
||||||
|
"for_minutes_state_help": "0 her değişikliği hemen sayar. Dakika ayarlanırsa yeni durumun önce o kadar sürmesi gerekir — kısa titremeler o zaman ne tetikler ne de sayılır.",
|
||||||
"qr_print_title": "QR kodlarını yazdır",
|
"qr_print_title": "QR kodlarını yazdır",
|
||||||
"qr_print_desc": "Kesip ekipmanınıza yapıştırmak için yazdırılabilir bir QR kodu sayfası oluşturun.",
|
"qr_print_desc": "Kesip ekipmanınıza yapıştırmak için yazdırılabilir bir QR kodu sayfası oluşturun.",
|
||||||
"qr_print_load": "Nesneleri yükle",
|
"qr_print_load": "Nesneleri yükle",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Zamanında tamamlama varsayılarak.",
|
"schedule_preview_ontime": "Zamanında tamamlama varsayılarak.",
|
||||||
"schedule_preview_ends": "(seri sona erer)",
|
"schedule_preview_ends": "(seri sona erer)",
|
||||||
"adopt_problem_responsible": "Devralınan tüm görevler için sorumlu kullanıcı (isteğe bağlı)",
|
"adopt_problem_responsible": "Devralınan tüm görevler için sorumlu kullanıcı (isteğe bağlı)",
|
||||||
|
"adopt_for_minutes_hint": "Yalnızca sorun bu süre boyunca devam ederse tetikle — 0 ilk titremeye tepki verir.",
|
||||||
"adopt_problem_configure": "Yapılandır",
|
"adopt_problem_configure": "Yapılandır",
|
||||||
"history_auto": "Otomatik",
|
"history_auto": "Otomatik",
|
||||||
"battery_fleet_title": "Pil filosu",
|
"battery_fleet_title": "Pil filosu",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Bir cihazı, daha düşük şarj bildirmeden burada hariç tutun — kendi kendine şarj olan bir süpürge ya da zaten kendisi uyaran bir telefon gibi.",
|
"battery_fleet_all_hint": "Bir cihazı, daha düşük şarj bildirmeden burada hariç tutun — kendi kendine şarj olan bir süpürge ya da zaten kendisi uyaran bir telefon gibi.",
|
||||||
"battery_fleet_add": "Pil ekle",
|
"battery_fleet_add": "Pil ekle",
|
||||||
"battery_fleet_add_hint": "Otomatik keşfin atladığı bir pil sensörü seçin — hemen listeye katılır.",
|
"battery_fleet_add_hint": "Otomatik keşfin atladığı bir pil sensörü seçin — hemen listeye katılır.",
|
||||||
|
"battery_fleet_track_self": "Kendi kendine şarj olan pilleri izle",
|
||||||
|
"battery_fleet_track_self_hint": "Telefonlar, süpürgeler ve kendi kendine şarj olan diğer cihazlar şarj edilebilir olarak görünür — seviye düşükken şarj istenir, asla yeni pil istenmez.",
|
||||||
"battery_fleet_status_low": "Düşük",
|
"battery_fleet_status_low": "Düşük",
|
||||||
"battery_fleet_status_soon": "Yakında",
|
"battery_fleet_status_soon": "Yakında",
|
||||||
"battery_fleet_status_ok": "İyi durumda",
|
"battery_fleet_status_ok": "İyi durumda",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Групувати за",
|
"group_by_label": "Групувати за",
|
||||||
"state_value_help": "Використовуйте значення стану HA (зазвичай у нижньому регістрі, напр. \"on\"/\"off\"). Регістр нормалізується при збереженні.",
|
"state_value_help": "Використовуйте значення стану HA (зазвичай у нижньому регістрі, напр. \"on\"/\"off\"). Регістр нормалізується при збереженні.",
|
||||||
"target_changes_help": "Кількість відповідних переходів, після яких тригер спрацює (за замовчуванням: 1).",
|
"target_changes_help": "Кількість відповідних переходів, після яких тригер спрацює (за замовчуванням: 1).",
|
||||||
|
"for_minutes_state_help": "0 — кожна зміна зараховується одразу. З хвилинами новий стан спершу має утриматися — короткі збої тоді не спрацьовують і не рахуються.",
|
||||||
"qr_print_title": "Друкувати QR-коди",
|
"qr_print_title": "Друкувати QR-коди",
|
||||||
"qr_print_desc": "Створи сторінку для друку з QR-кодами, які можна вирізати та наклеїти на обладнання.",
|
"qr_print_desc": "Створи сторінку для друку з QR-кодами, які можна вирізати та наклеїти на обладнання.",
|
||||||
"qr_print_load": "Завантажити об'єкти",
|
"qr_print_load": "Завантажити об'єкти",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Не було необхідно",
|
"quick_complete_defaults_feedback_not_needed": "Не було необхідно",
|
||||||
"quick_complete_success": "Швидко позначено виконаним",
|
"quick_complete_success": "Швидко позначено виконаним",
|
||||||
"trigger_replaced": "Тригер замінено",
|
"trigger_replaced": "Тригер замінено",
|
||||||
|
"trigger_removed": "Тригер видалено",
|
||||||
"add": "Додати",
|
"add": "Додати",
|
||||||
"show_stats": "Показати статистику + графіки",
|
"show_stats": "Показати статистику + графіки",
|
||||||
"hide_stats": "Сховати статистику",
|
"hide_stats": "Сховати статистику",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "За умови вчасного виконання.",
|
"schedule_preview_ontime": "За умови вчасного виконання.",
|
||||||
"schedule_preview_ends": "(серія завершується)",
|
"schedule_preview_ends": "(серія завершується)",
|
||||||
"adopt_problem_responsible": "Відповідальний користувач для всіх прийнятих завдань (необов'язково)",
|
"adopt_problem_responsible": "Відповідальний користувач для всіх прийнятих завдань (необов'язково)",
|
||||||
|
"adopt_for_minutes_hint": "Спрацьовувати лише якщо проблема триває цей час — 0 реагує на перший збій.",
|
||||||
"adopt_problem_configure": "Налаштувати",
|
"adopt_problem_configure": "Налаштувати",
|
||||||
"history_auto": "Автоматично",
|
"history_auto": "Автоматично",
|
||||||
"battery_fleet_title": "Парк батарей",
|
"battery_fleet_title": "Парк батарей",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Вилучіть пристрій тут ще до того, як він повідомить про низький заряд — робот-пилосос, що заряджається сам, або телефон, який попереджає сам.",
|
"battery_fleet_all_hint": "Вилучіть пристрій тут ще до того, як він повідомить про низький заряд — робот-пилосос, що заряджається сам, або телефон, який попереджає сам.",
|
||||||
"battery_fleet_add": "Додати батарею",
|
"battery_fleet_add": "Додати батарею",
|
||||||
"battery_fleet_add_hint": "Виберіть датчик батареї, який автоматичне виявлення пропустило — він одразу з'явиться у списку.",
|
"battery_fleet_add_hint": "Виберіть датчик батареї, який автоматичне виявлення пропустило — він одразу з'явиться у списку.",
|
||||||
|
"battery_fleet_track_self": "Відстежувати самозарядні батареї",
|
||||||
|
"battery_fleet_track_self_hint": "Телефони, пилососи та інші самозарядні пристрої відображаються як акумулятори — за низького заряду потрібна зарядка, а не нові батарейки.",
|
||||||
"battery_fleet_status_low": "Низький",
|
"battery_fleet_status_low": "Низький",
|
||||||
"battery_fleet_status_soon": "Незабаром",
|
"battery_fleet_status_soon": "Незабаром",
|
||||||
"battery_fleet_status_ok": "У нормі",
|
"battery_fleet_status_ok": "У нормі",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "即将到期",
|
"due_soon": "即将到期",
|
||||||
"triggered": "已触发",
|
"triggered": "已触发",
|
||||||
"trigger_replaced": "触发器已更换",
|
"trigger_replaced": "触发器已更换",
|
||||||
|
"trigger_removed": "触发器已移除",
|
||||||
"ok": "正常",
|
"ok": "正常",
|
||||||
"all": "全部",
|
"all": "全部",
|
||||||
"new_object": "+ 新建维护项",
|
"new_object": "+ 新建维护项",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "分组依据",
|
"group_by_label": "分组依据",
|
||||||
"state_value_help": "使用 HA 状态值(通常为小写,例如 \"on\"/\"off\")。保存时大小写会自动规范化。",
|
"state_value_help": "使用 HA 状态值(通常为小写,例如 \"on\"/\"off\")。保存时大小写会自动规范化。",
|
||||||
"target_changes_help": "触发前需要满足的状态转换次数(默认为 1)。",
|
"target_changes_help": "触发前需要满足的状态转换次数(默认为 1)。",
|
||||||
|
"for_minutes_state_help": "0 表示每次变化立即计数。设置分钟后,新状态需先保持该时长——短暂闪变既不触发也不计数。",
|
||||||
"qr_print_title": "打印二维码",
|
"qr_print_title": "打印二维码",
|
||||||
"qr_print_desc": "生成一张包含多个二维码的可打印页面,方便裁剪并贴在设备上。",
|
"qr_print_desc": "生成一张包含多个二维码的可打印页面,方便裁剪并贴在设备上。",
|
||||||
"qr_print_load": "加载设备",
|
"qr_print_load": "加载设备",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "假设按时完成。",
|
"schedule_preview_ontime": "假设按时完成。",
|
||||||
"schedule_preview_ends": "(系列结束)",
|
"schedule_preview_ends": "(系列结束)",
|
||||||
"adopt_problem_responsible": "所有采用任务的负责用户(可选)",
|
"adopt_problem_responsible": "所有采用任务的负责用户(可选)",
|
||||||
|
"adopt_for_minutes_hint": "问题持续此时长后才触发——0 表示对首次闪变立即响应。",
|
||||||
"adopt_problem_configure": "配置",
|
"adopt_problem_configure": "配置",
|
||||||
"history_auto": "自动",
|
"history_auto": "自动",
|
||||||
"battery_fleet_title": "电池群",
|
"battery_fleet_title": "电池群",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "在设备报告电量不足之前就可以在这里将其排除——例如会自行充电的扫地机器人,或本身就会提醒你的手机。",
|
"battery_fleet_all_hint": "在设备报告电量不足之前就可以在这里将其排除——例如会自行充电的扫地机器人,或本身就会提醒你的手机。",
|
||||||
"battery_fleet_add": "添加电池",
|
"battery_fleet_add": "添加电池",
|
||||||
"battery_fleet_add_hint": "选择自动发现遗漏的电池传感器——它会立即加入列表。",
|
"battery_fleet_add_hint": "选择自动发现遗漏的电池传感器——它会立即加入列表。",
|
||||||
|
"battery_fleet_track_self": "跟踪自充电电池",
|
||||||
|
"battery_fleet_track_self_hint": "手机、吸尘器等自充电设备显示为可充电电池——电量低时提示充电,而不是更换电池。",
|
||||||
"battery_fleet_status_low": "电量低",
|
"battery_fleet_status_low": "电量低",
|
||||||
"battery_fleet_status_soon": "即将",
|
"battery_fleet_status_soon": "即将",
|
||||||
"battery_fleet_status_ok": "正常",
|
"battery_fleet_status_ok": "正常",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
type CalendarEvent,
|
type CalendarEvent,
|
||||||
} from "./helpers/calendar-bucket";
|
} from "./helpers/calendar-bucket";
|
||||||
import { calendarStyles } from "./calendar-styles";
|
import { calendarStyles } from "./calendar-styles";
|
||||||
import { sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays, langOf } from "./styles";
|
import { syncLocaleFromHass, sharedStyles, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays, langOf } from "./styles";
|
||||||
import { registerCustomCard } from "./helpers/register-card";
|
import { registerCustomCard } from "./helpers/register-card";
|
||||||
import { openHistoryEditDialog, openTaskQuickActions } from "./dialog-mount";
|
import { openHistoryEditDialog, openTaskQuickActions } from "./dialog-mount";
|
||||||
import type {
|
import type {
|
||||||
@@ -139,12 +139,7 @@ export class MaintenanceCalendarCard extends LitElement {
|
|||||||
|
|
||||||
updated(changedProps: Map<string, unknown>): void {
|
updated(changedProps: Map<string, unknown>): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
// Dates/times follow the HA profile format, not just the language (#97).
|
syncLocaleFromHass(this, changedProps);
|
||||||
if (changedProps.has("hass")) setDateTimePrefs(this.hass?.locale);
|
|
||||||
const lang = this.hass?.language;
|
|
||||||
if (lang && !isLocaleLoaded(lang)) {
|
|
||||||
ensureLocale(lang).then(() => this.requestUpdate());
|
|
||||||
}
|
|
||||||
if (changedProps.has("hass") && this.hass) {
|
if (changedProps.has("hass") && this.hass) {
|
||||||
if (!this._dataLoaded) {
|
if (!this._dataLoaded) {
|
||||||
this._dataLoaded = true;
|
this._dataLoaded = true;
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import { LitElement, html, css, nothing } from "lit";
|
|||||||
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
||||||
import { hydrateObjects } from "./helpers/hydrate-objects";
|
import { hydrateObjects } from "./helpers/hydrate-objects";
|
||||||
import { property, state } from "lit/decorators.js";
|
import { property, state } from "lit/decorators.js";
|
||||||
import { sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays, langOf } from "./styles";
|
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, t, ensureLocale, isLocaleLoaded, setDateTimePrefs, formatDueDays, langOf } from "./styles";
|
||||||
import { openSignedDocument } from "./helpers/document-url";
|
import { openSignedDocument } from "./helpers/document-url";
|
||||||
|
import { isSafeHttpUrl } from "./helpers/url";
|
||||||
import { registerCustomCard } from "./helpers/register-card";
|
import { registerCustomCard } from "./helpers/register-card";
|
||||||
import type {
|
import type {
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
@@ -20,6 +21,9 @@ import { UserService } from "./user-service";
|
|||||||
import { partsForCompletion } from "./helpers/shared-parts";
|
import { partsForCompletion } from "./helpers/shared-parts";
|
||||||
import "./maintenance-card-editor";
|
import "./maintenance-card-editor";
|
||||||
import "./components/complete-dialog";
|
import "./components/complete-dialog";
|
||||||
|
// The Battery Fleet card ships in this globally-loaded bundle so it is
|
||||||
|
// available on every dashboard without another extra_module_url entry.
|
||||||
|
import "./components/battery-fleet-card";
|
||||||
import {
|
import {
|
||||||
openCreateObjectDialog,
|
openCreateObjectDialog,
|
||||||
openCreateTaskDialog,
|
openCreateTaskDialog,
|
||||||
@@ -110,12 +114,7 @@ export class MaintenanceSupporterCard extends LitElement {
|
|||||||
|
|
||||||
updated(changedProps: Map<string, unknown>): void {
|
updated(changedProps: Map<string, unknown>): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
// Dates/times follow the HA profile format, not just the language (#97).
|
syncLocaleFromHass(this, changedProps);
|
||||||
if (changedProps.has("hass")) setDateTimePrefs(this.hass?.locale);
|
|
||||||
const lang = this.hass?.language;
|
|
||||||
if (lang && !isLocaleLoaded(lang)) {
|
|
||||||
ensureLocale(lang).then(() => this.requestUpdate());
|
|
||||||
}
|
|
||||||
// Assignee names: resolved once, only when the config allows it AND some
|
// Assignee names: resolved once, only when the config allows it AND some
|
||||||
// task actually carries a responsible user. The condition is checked
|
// task actually carries a responsible user. The condition is checked
|
||||||
// synchronously here — an unconditional call whose promise triggered a
|
// synchronously here — an unconditional call whose promise triggered a
|
||||||
@@ -239,7 +238,10 @@ export class MaintenanceSupporterCard extends LitElement {
|
|||||||
* (the same route the panel uses, so it works in the Companion app). */
|
* (the same route the panel uses, so it works in the Companion app). */
|
||||||
private async _openDoc(doc: CardDoc): Promise<void> {
|
private async _openDoc(doc: CardDoc): Promise<void> {
|
||||||
if (doc.kind === "weblink" && doc.url) {
|
if (doc.kind === "weblink" && doc.url) {
|
||||||
window.open(doc.url, "_blank", "noopener");
|
// Same guarded sink as every other weblink opener: never hand a
|
||||||
|
// non-http(s) URL to window.open (defense-in-depth against a link
|
||||||
|
// stored before the server-side scheme check existed).
|
||||||
|
if (isSafeHttpUrl(doc.url)) window.open(doc.url, "_blank", "noopener");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { isSafeHttpUrl } from "./helpers/url";
|
|||||||
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
import { applySubscriptionEvent, type SubscriptionEvent } from "./helpers/subscription-merge";
|
||||||
import { isStaleBundle } from "./helpers/bundle-version";
|
import { isStaleBundle } from "./helpers/bundle-version";
|
||||||
import { customElement, property, state } from "lit/decorators.js";
|
import { customElement, property, state } from "lit/decorators.js";
|
||||||
import { sharedStyles, STATUS_COLORS, STATUS_ICONS, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, formatDate, formatDueDays, formatInterval, formatRecurrence, setDateTimePrefs, langOf } from "./styles";
|
import { syncLocaleFromHass, sharedStyles, STATUS_COLORS, STATUS_ICONS, DEFAULT_CURRENCY_SYMBOL, t, ensureLocale, isLocaleLoaded, formatDate, formatDueDays, formatInterval, formatRecurrence, setDateTimePrefs, langOf } from "./styles";
|
||||||
import { LS_KEYS, lsGet, lsSet } from "./helpers/storage-keys";
|
import { LS_KEYS, lsGet, lsSet } from "./helpers/storage-keys";
|
||||||
import { openSignedDocument, signApiPath } from "./helpers/document-url";
|
import { openHtmlInNewTab, openSignedDocument, signApiPath } from "./helpers/document-url";
|
||||||
import { readObjectsCache, writeObjectsCache } from "./helpers/objects-cache";
|
import { readObjectsCache, writeObjectsCache } from "./helpers/objects-cache";
|
||||||
import { hydrateObjects } from "./helpers/hydrate-objects";
|
import { hydrateObjects } from "./helpers/hydrate-objects";
|
||||||
import { daysProgress } from "./helpers/interval";
|
import { daysProgress } from "./helpers/interval";
|
||||||
@@ -373,15 +373,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
|||||||
|
|
||||||
updated(changedProps: Map<string, unknown>): void {
|
updated(changedProps: Map<string, unknown>): void {
|
||||||
super.updated(changedProps);
|
super.updated(changedProps);
|
||||||
// Dates/times follow the HA profile format, not just the language (#97).
|
syncLocaleFromHass(this, changedProps);
|
||||||
if (changedProps.has("hass")) setDateTimePrefs(this.hass?.locale);
|
|
||||||
// Lazy-load the user's UI language (non-EN tables aren't bundled) and
|
|
||||||
// re-render once it arrives. EN is bundled, so strings read in English
|
|
||||||
// until then rather than as raw keys.
|
|
||||||
const lang = this.hass?.language;
|
|
||||||
if (lang && !isLocaleLoaded(lang)) {
|
|
||||||
ensureLocale(lang).then(() => this.requestUpdate());
|
|
||||||
}
|
|
||||||
if (changedProps.has("hass") && this.hass) {
|
if (changedProps.has("hass") && this.hass) {
|
||||||
if (!this._dataLoaded) {
|
if (!this._dataLoaded) {
|
||||||
this._dataLoaded = true;
|
this._dataLoaded = true;
|
||||||
@@ -1477,9 +1469,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
|||||||
this._currencySymbol,
|
this._currencySymbol,
|
||||||
new Date().toISOString(),
|
new Date().toISOString(),
|
||||||
);
|
);
|
||||||
const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
openHtmlInNewTab(html);
|
||||||
window.open(url, "_blank");
|
|
||||||
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _duplicateObject(entryId: string): Promise<void> {
|
private async _duplicateObject(entryId: string): Promise<void> {
|
||||||
@@ -1843,9 +1833,7 @@ export class MaintenanceSupporterPanel extends LitElement {
|
|||||||
new Date().toISOString(),
|
new Date().toISOString(),
|
||||||
partsLines,
|
partsLines,
|
||||||
);
|
);
|
||||||
const url = URL.createObjectURL(new Blob([html], { type: "text/html" }));
|
openHtmlInNewTab(html);
|
||||||
window.open(url, "_blank");
|
|
||||||
setTimeout(() => URL.revokeObjectURL(url), 60000);
|
|
||||||
} finally {
|
} finally {
|
||||||
this._actionLoading = false;
|
this._actionLoading = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ export interface HistoryContext {
|
|||||||
readingDelta?: (entry: HistoryEntry) => number | null;
|
readingDelta?: (entry: HistoryEntry) => number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const _FILTER_TYPES = ["completed", "skipped", "missed", "reset", "triggered"] as const;
|
// Every HistoryEntryType is filterable (drift audit 2026-08: the repair-flow
|
||||||
|
// types were missing, so their entries could not be isolated). Chips only
|
||||||
|
// render for types present in the task's history, so the two rare ones cost
|
||||||
|
// nothing on normal tasks.
|
||||||
|
const _FILTER_TYPES = ["completed", "skipped", "missed", "reset", "triggered", "trigger_replaced", "trigger_removed"] as const;
|
||||||
|
|
||||||
export function renderHistoryFilters(task: MaintenanceTask, ctx: HistoryContext) {
|
export function renderHistoryFilters(task: MaintenanceTask, ctx: HistoryContext) {
|
||||||
const L = ctx.lang;
|
const L = ctx.lang;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { css } from "lit";
|
import { css } from "lit";
|
||||||
import EN from "./locales/en.json";
|
import EN from "./locales/en.json";
|
||||||
|
import { BUNDLE_VERSION } from "./helpers/bundle-version";
|
||||||
|
|
||||||
// Display fallback when the backend hasn't sent a currency symbol. The backend
|
// Display fallback when the backend hasn't sent a currency symbol. The backend
|
||||||
// derives the real symbol from const.BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY].
|
// derives the real symbol from const.BUDGET_CURRENCIES[DEFAULT_BUDGET_CURRENCY].
|
||||||
@@ -47,8 +48,21 @@ const _localeGlobals: LocaleGlobals = (() => {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
const STORE = _localeGlobals.store;
|
const STORE = _localeGlobals.store;
|
||||||
// Each copy guarantees the bundled English fallback is present.
|
|
||||||
if (!STORE.en) STORE.en = EN as Translations;
|
/** Seed/extend the shared English table with this bundle's copy.
|
||||||
|
*
|
||||||
|
* MERGE, not first-wins (#135 regression): after an update, a cached app
|
||||||
|
* shell can still list an OLD sibling bundle (the card) next to the fresh
|
||||||
|
* panel. First-wins let the stale bundle's EN — missing every new key —
|
||||||
|
* claim the store, and the fresh panel rendered raw keys
|
||||||
|
* ("BATTERY_FLEET_ADD"). Existing entries win conflicts (steady state is
|
||||||
|
* unchanged); every bundle contributes the keys it knows, so a newer
|
||||||
|
* bundle's keys can never be shadowed by an older first-loader.
|
||||||
|
*/
|
||||||
|
export function seedEnglish(en: Record<string, string>): void {
|
||||||
|
STORE.en = Object.assign({}, en, STORE.en ?? {});
|
||||||
|
}
|
||||||
|
seedEnglish(EN as Translations);
|
||||||
|
|
||||||
/** Languages available as runtime-loaded JSON. Keep in sync with locales/. */
|
/** Languages available as runtime-loaded JSON. Keep in sync with locales/. */
|
||||||
const SUPPORTED_LANGS = new Set<string>([
|
const SUPPORTED_LANGS = new Set<string>([
|
||||||
@@ -79,6 +93,22 @@ export function t(key: string, lang?: string): string {
|
|||||||
return STORE[l]?.[key] ?? STORE.en[key] ?? key;
|
return STORE[l]?.[key] ?? STORE.en[key] ?? key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The per-`updated()` locale boot every top-level surface needs: date/time
|
||||||
|
* prefs follow the HA profile (#97), and the user's UI language is lazily
|
||||||
|
* fetched with a re-render once it arrives (EN is bundled, so strings read
|
||||||
|
* in English until then rather than as raw keys). Was copy-pasted across the
|
||||||
|
* panel and both cards (drift audit 2026-08). */
|
||||||
|
export function syncLocaleFromHass(
|
||||||
|
host: { hass?: { language?: string; locale?: unknown }; requestUpdate: () => void },
|
||||||
|
changedProps: Map<string, unknown>,
|
||||||
|
): void {
|
||||||
|
if (changedProps.has("hass")) setDateTimePrefs(host.hass?.locale as Parameters<typeof setDateTimePrefs>[0]);
|
||||||
|
const lang = host.hass?.language;
|
||||||
|
if (lang && !isLocaleLoaded(lang)) {
|
||||||
|
ensureLocale(lang).then(() => host.requestUpdate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The UI language for a hass object — the ONE rule every component's `_lang`
|
* The UI language for a hass object — the ONE rule every component's `_lang`
|
||||||
* getter delegates to. Before this helper the 23 hand-copied getters had
|
* getter delegates to. Before this helper the 23 hand-copied getters had
|
||||||
@@ -108,7 +138,11 @@ export function ensureLocale(lang?: string): Promise<void> {
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
if (!(l in _localeInflight)) {
|
if (!(l in _localeInflight)) {
|
||||||
_localeInflight[l] = fetch(`${LOCALES_BASE}/${l}.json`)
|
// Version-busted (#135, same family as #124): the locale files are served
|
||||||
|
// without Cache-Control, so browsers cache them heuristically — after an
|
||||||
|
// update a stale table would miss every new key and silently fall back to
|
||||||
|
// English for them. "dev" builds keep a stable URL.
|
||||||
|
_localeInflight[l] = fetch(`${LOCALES_BASE}/${l}.json?v=${BUNDLE_VERSION}`)
|
||||||
.then((r) => (r.ok ? r.json() : null))
|
.then((r) => (r.ok ? r.json() : null))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -1067,48 +1101,9 @@ export const sharedStyles = css`
|
|||||||
.confidence-dot.medium { background: var(--warning-color, #ff9800); }
|
.confidence-dot.medium { background: var(--warning-color, #ff9800); }
|
||||||
.confidence-dot.high { background: var(--success-color, #4caf50); }
|
.confidence-dot.high { background: var(--success-color, #4caf50); }
|
||||||
|
|
||||||
/* Feedback toggle buttons in complete dialog */
|
/* (The complete-dialog's .feedback-* rules live in complete-dialog.ts —
|
||||||
.feedback-section {
|
this sheet carried a dead byte-identical copy until the 2026-08 drift
|
||||||
display: flex;
|
audit; complete-dialog never imports sharedStyles.) */
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 0;
|
|
||||||
border-top: 1px solid var(--divider-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-label {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-btn {
|
|
||||||
flex: 1;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border: 1px solid var(--divider-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--card-background-color, #fff);
|
|
||||||
color: var(--primary-text-color);
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-btn:hover {
|
|
||||||
background: var(--secondary-background-color, #f5f5f5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-btn.selected {
|
|
||||||
background: var(--primary-color);
|
|
||||||
color: var(--text-primary-color, #fff);
|
|
||||||
border-color: var(--primary-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Seasonal chart */
|
/* Seasonal chart */
|
||||||
.seasonal-chart {
|
.seasonal-chart {
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Seskupit podle",
|
"group_by_label": "Seskupit podle",
|
||||||
"state_value_help": "Použijte hodnotu stavu HA (obvykle malými písmeny, např. \"on\"/\"off\"). Velikost písmen se při uložení normalizuje.",
|
"state_value_help": "Použijte hodnotu stavu HA (obvykle malými písmeny, např. \"on\"/\"off\"). Velikost písmen se při uložení normalizuje.",
|
||||||
"target_changes_help": "Počet odpovídajících přechodů, po kterých se trigger spustí (výchozí: 1).",
|
"target_changes_help": "Počet odpovídajících přechodů, po kterých se trigger spustí (výchozí: 1).",
|
||||||
|
"for_minutes_state_help": "0 počítá každou změnu ihned. S minutami musí nový stav nejprve vydržet — krátké bliknutí pak nespustí ani se nepočítá.",
|
||||||
"qr_print_title": "Tisk QR kódů",
|
"qr_print_title": "Tisk QR kódů",
|
||||||
"qr_print_desc": "Vygeneruj tiskovou stránku s QR kódy k vystřižení a nalepení na zařízení.",
|
"qr_print_desc": "Vygeneruj tiskovou stránku s QR kódy k vystřižení a nalepení na zařízení.",
|
||||||
"qr_print_load": "Načíst objekty",
|
"qr_print_load": "Načíst objekty",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Nebylo potřeba",
|
"quick_complete_defaults_feedback_not_needed": "Nebylo potřeba",
|
||||||
"quick_complete_success": "Rychle označeno jako hotové",
|
"quick_complete_success": "Rychle označeno jako hotové",
|
||||||
"trigger_replaced": "Spouštěč nahrazen",
|
"trigger_replaced": "Spouštěč nahrazen",
|
||||||
|
"trigger_removed": "Spouštěč odebrán",
|
||||||
"add": "Přidat",
|
"add": "Přidat",
|
||||||
"show_stats": "Zobrazit statistiky + grafy",
|
"show_stats": "Zobrazit statistiky + grafy",
|
||||||
"hide_stats": "Skrýt statistiky",
|
"hide_stats": "Skrýt statistiky",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Za předpokladu včasného dokončení.",
|
"schedule_preview_ontime": "Za předpokladu včasného dokončení.",
|
||||||
"schedule_preview_ends": "(série končí)",
|
"schedule_preview_ends": "(série končí)",
|
||||||
"adopt_problem_responsible": "Odpovědná osoba pro všechny převzaté úkoly (volitelné)",
|
"adopt_problem_responsible": "Odpovědná osoba pro všechny převzaté úkoly (volitelné)",
|
||||||
|
"adopt_for_minutes_hint": "Spustit až když problém trvá takto dlouho — 0 reaguje na první bliknutí.",
|
||||||
"adopt_problem_configure": "Konfigurovat",
|
"adopt_problem_configure": "Konfigurovat",
|
||||||
"history_auto": "Automaticky",
|
"history_auto": "Automaticky",
|
||||||
"battery_fleet_title": "Flotila baterií",
|
"battery_fleet_title": "Flotila baterií",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Vylučte zařízení už dřív, než vůbec ohlásí nízkou úroveň — vysavač, který se sám nabíjí, nebo telefon, který varuje sám.",
|
"battery_fleet_all_hint": "Vylučte zařízení už dřív, než vůbec ohlásí nízkou úroveň — vysavač, který se sám nabíjí, nebo telefon, který varuje sám.",
|
||||||
"battery_fleet_add": "Přidat baterii",
|
"battery_fleet_add": "Přidat baterii",
|
||||||
"battery_fleet_add_hint": "Vyberte senzor baterie, který automatická detekce přehlédla — okamžitě se objeví v seznamu.",
|
"battery_fleet_add_hint": "Vyberte senzor baterie, který automatická detekce přehlédla — okamžitě se objeví v seznamu.",
|
||||||
|
"battery_fleet_track_self": "Sledovat samodobíjecí baterie",
|
||||||
|
"battery_fleet_track_self_hint": "Telefony, vysavače a další samodobíjecí zařízení se zobrazují jako akumulátory — při nízkém stavu žádají o nabití, nikdy o nové články.",
|
||||||
"battery_fleet_status_low": "Nízká",
|
"battery_fleet_status_low": "Nízká",
|
||||||
"battery_fleet_status_soon": "Brzy",
|
"battery_fleet_status_soon": "Brzy",
|
||||||
"battery_fleet_status_ok": "V pořádku",
|
"battery_fleet_status_ok": "V pořádku",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Snart forfalden",
|
"due_soon": "Snart forfalden",
|
||||||
"triggered": "Udløst",
|
"triggered": "Udløst",
|
||||||
"trigger_replaced": "Udløser erstattet",
|
"trigger_replaced": "Udløser erstattet",
|
||||||
|
"trigger_removed": "Udløser fjernet",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"new_object": "+ Nyt objekt",
|
"new_object": "+ Nyt objekt",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Grupper efter",
|
"group_by_label": "Grupper efter",
|
||||||
"state_value_help": "Brug HA-tilstandsværdien (normalt med små bogstaver, f.eks. \"on\"/\"off\"). Store/små bogstaver normaliseres ved lagring.",
|
"state_value_help": "Brug HA-tilstandsværdien (normalt med små bogstaver, f.eks. \"on\"/\"off\"). Store/små bogstaver normaliseres ved lagring.",
|
||||||
"target_changes_help": "Antal matchende overgange, før udløseren aktiveres (standard: 1).",
|
"target_changes_help": "Antal matchende overgange, før udløseren aktiveres (standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 tæller hver ændring straks. Med minutter skal den nye tilstand først holde — korte blink hverken udløser eller tælles så.",
|
||||||
"qr_print_title": "Udskriv QR-koder",
|
"qr_print_title": "Udskriv QR-koder",
|
||||||
"qr_print_desc": "Generer en udskriftsklar side med QR-koder, der kan klippes ud og sættes på dit udstyr.",
|
"qr_print_desc": "Generer en udskriftsklar side med QR-koder, der kan klippes ud og sættes på dit udstyr.",
|
||||||
"qr_print_load": "Indlæs objekter",
|
"qr_print_load": "Indlæs objekter",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Forudsat rettidig fuldførelse.",
|
"schedule_preview_ontime": "Forudsat rettidig fuldførelse.",
|
||||||
"schedule_preview_ends": "(serien slutter)",
|
"schedule_preview_ends": "(serien slutter)",
|
||||||
"adopt_problem_responsible": "Ansvarlig bruger for alle overtagne opgaver (valgfrit)",
|
"adopt_problem_responsible": "Ansvarlig bruger for alle overtagne opgaver (valgfrit)",
|
||||||
|
"adopt_for_minutes_hint": "Udløs først når problemet har varet så længe — 0 reagerer på første blink.",
|
||||||
"adopt_problem_configure": "Konfigurer",
|
"adopt_problem_configure": "Konfigurer",
|
||||||
"history_auto": "Automatisk",
|
"history_auto": "Automatisk",
|
||||||
"battery_fleet_title": "Batteriflåde",
|
"battery_fleet_title": "Batteriflåde",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Udeluk en enhed her, før den overhovedet melder lavt niveau — en støvsuger, der lader sig selv, eller en telefon, der advarer af sig selv.",
|
"battery_fleet_all_hint": "Udeluk en enhed her, før den overhovedet melder lavt niveau — en støvsuger, der lader sig selv, eller en telefon, der advarer af sig selv.",
|
||||||
"battery_fleet_add": "Tilføj et batteri",
|
"battery_fleet_add": "Tilføj et batteri",
|
||||||
"battery_fleet_add_hint": "Vælg en batterisensor, som den automatiske registrering overså — den kommer straks på listen.",
|
"battery_fleet_add_hint": "Vælg en batterisensor, som den automatiske registrering overså — den kommer straks på listen.",
|
||||||
|
"battery_fleet_track_self": "Spor selvopladende batterier",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoner, støvsugere og andre selvopladende enheder vises som genopladelige — ved lavt niveau bedes om opladning, aldrig om nye batterier.",
|
||||||
"battery_fleet_status_low": "Lav",
|
"battery_fleet_status_low": "Lav",
|
||||||
"battery_fleet_status_soon": "Snart",
|
"battery_fleet_status_soon": "Snart",
|
||||||
"battery_fleet_status_ok": "I orden",
|
"battery_fleet_status_ok": "I orden",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Bald fällig",
|
"due_soon": "Bald fällig",
|
||||||
"triggered": "Ausgelöst",
|
"triggered": "Ausgelöst",
|
||||||
"trigger_replaced": "Auslöser ersetzt",
|
"trigger_replaced": "Auslöser ersetzt",
|
||||||
|
"trigger_removed": "Trigger entfernt",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"new_object": "+ Neues Objekt",
|
"new_object": "+ Neues Objekt",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Gruppieren nach",
|
"group_by_label": "Gruppieren nach",
|
||||||
"state_value_help": "Verwende den HA-Zustandswert (meist kleingeschrieben, z. B. \"on\"/\"off\"). Groß-/Kleinschreibung wird beim Speichern normalisiert.",
|
"state_value_help": "Verwende den HA-Zustandswert (meist kleingeschrieben, z. B. \"on\"/\"off\"). Groß-/Kleinschreibung wird beim Speichern normalisiert.",
|
||||||
"target_changes_help": "Anzahl der passenden Übergänge, nach denen der Trigger auslöst (Standard: 1).",
|
"target_changes_help": "Anzahl der passenden Übergänge, nach denen der Trigger auslöst (Standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 zählt jede Änderung sofort. Mit Minuten muss der neue Zustand erst so lange bestehen — kurzes Flackern löst dann weder aus noch zählt es.",
|
||||||
"qr_print_title": "QR-Codes drucken",
|
"qr_print_title": "QR-Codes drucken",
|
||||||
"qr_print_desc": "Erzeuge eine Druckseite mit QR-Codes zum Ausschneiden und Anbringen an den Geräten.",
|
"qr_print_desc": "Erzeuge eine Druckseite mit QR-Codes zum Ausschneiden und Anbringen an den Geräten.",
|
||||||
"qr_print_load": "Objekte laden",
|
"qr_print_load": "Objekte laden",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Bei pünktlichem Abschluss.",
|
"schedule_preview_ontime": "Bei pünktlichem Abschluss.",
|
||||||
"schedule_preview_ends": "(Serie endet)",
|
"schedule_preview_ends": "(Serie endet)",
|
||||||
"adopt_problem_responsible": "Verantwortliche Person für alle übernommenen Aufgaben (optional)",
|
"adopt_problem_responsible": "Verantwortliche Person für alle übernommenen Aufgaben (optional)",
|
||||||
|
"adopt_for_minutes_hint": "Erst auslösen, wenn das Problem so lange besteht — 0 reagiert auf das erste Flackern.",
|
||||||
"adopt_problem_configure": "Konfigurieren",
|
"adopt_problem_configure": "Konfigurieren",
|
||||||
"history_auto": "Automatisch",
|
"history_auto": "Automatisch",
|
||||||
"battery_fleet_title": "Batterie-Flotte",
|
"battery_fleet_title": "Batterie-Flotte",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Ein Gerät hier ausschließen, bevor es überhaupt schwach meldet — etwa ein Sauger, der sich selbst lädt, oder ein Telefon, das von sich aus warnt.",
|
"battery_fleet_all_hint": "Ein Gerät hier ausschließen, bevor es überhaupt schwach meldet — etwa ein Sauger, der sich selbst lädt, oder ein Telefon, das von sich aus warnt.",
|
||||||
"battery_fleet_add": "Batterie hinzufügen",
|
"battery_fleet_add": "Batterie hinzufügen",
|
||||||
"battery_fleet_add_hint": "Einen Batteriesensor wählen, den die automatische Erkennung übersehen hat — er erscheint sofort in der Liste.",
|
"battery_fleet_add_hint": "Einen Batteriesensor wählen, den die automatische Erkennung übersehen hat — er erscheint sofort in der Liste.",
|
||||||
|
"battery_fleet_track_self": "Selbstladende Batterien mitverfolgen",
|
||||||
|
"battery_fleet_track_self_hint": "Telefone, Staubsauger und andere selbstladende Geräte erscheinen als Akkus — bei niedrigem Stand wird ans Laden erinnert, nie an neue Zellen.",
|
||||||
"battery_fleet_status_low": "Schwach",
|
"battery_fleet_status_low": "Schwach",
|
||||||
"battery_fleet_status_soon": "Bald",
|
"battery_fleet_status_soon": "Bald",
|
||||||
"battery_fleet_status_ok": "In Ordnung",
|
"battery_fleet_status_ok": "In Ordnung",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Due Soon",
|
"due_soon": "Due Soon",
|
||||||
"triggered": "Triggered",
|
"triggered": "Triggered",
|
||||||
"trigger_replaced": "Trigger replaced",
|
"trigger_replaced": "Trigger replaced",
|
||||||
|
"trigger_removed": "Trigger removed",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
"new_object": "+ New Object",
|
"new_object": "+ New Object",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Group by",
|
"group_by_label": "Group by",
|
||||||
"state_value_help": "Use the HA state value (usually lowercase, e.g. \"on\"/\"off\"). Case is normalised on save.",
|
"state_value_help": "Use the HA state value (usually lowercase, e.g. \"on\"/\"off\"). Case is normalised on save.",
|
||||||
"target_changes_help": "Number of matching transitions before the trigger fires (default: 1).",
|
"target_changes_help": "Number of matching transitions before the trigger fires (default: 1).",
|
||||||
|
"for_minutes_state_help": "0 counts every change immediately. Set minutes and the new state must hold that long first — brief flickers then neither trigger nor count.",
|
||||||
"qr_print_title": "Print QR codes",
|
"qr_print_title": "Print QR codes",
|
||||||
"qr_print_desc": "Generate a printable page of QR codes to cut out and stick on your equipment.",
|
"qr_print_desc": "Generate a printable page of QR codes to cut out and stick on your equipment.",
|
||||||
"qr_print_load": "Load objects",
|
"qr_print_load": "Load objects",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Assuming on-time completion.",
|
"schedule_preview_ontime": "Assuming on-time completion.",
|
||||||
"schedule_preview_ends": "(series ends)",
|
"schedule_preview_ends": "(series ends)",
|
||||||
"adopt_problem_responsible": "Responsible user for all adopted tasks (optional)",
|
"adopt_problem_responsible": "Responsible user for all adopted tasks (optional)",
|
||||||
|
"adopt_for_minutes_hint": "Only trigger once the problem has persisted this long — 0 reacts to the first flicker.",
|
||||||
"adopt_problem_configure": "Configure",
|
"adopt_problem_configure": "Configure",
|
||||||
"history_auto": "Automatic",
|
"history_auto": "Automatic",
|
||||||
"battery_fleet_title": "Battery fleet",
|
"battery_fleet_title": "Battery fleet",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Exclude a device here to drop it from the fleet before it ever reports low — a vacuum that recharges itself, or a phone that warns you on its own.",
|
"battery_fleet_all_hint": "Exclude a device here to drop it from the fleet before it ever reports low — a vacuum that recharges itself, or a phone that warns you on its own.",
|
||||||
"battery_fleet_add": "Add a battery",
|
"battery_fleet_add": "Add a battery",
|
||||||
"battery_fleet_add_hint": "Pick a battery sensor the automatic discovery missed — it joins the roster immediately.",
|
"battery_fleet_add_hint": "Pick a battery sensor the automatic discovery missed — it joins the roster immediately.",
|
||||||
|
"battery_fleet_track_self": "Track self-charging batteries",
|
||||||
|
"battery_fleet_track_self_hint": "Phones, vacuums and other devices that recharge themselves appear as rechargeables — a low one asks for a charge, never for new cells.",
|
||||||
"battery_fleet_status_low": "Low",
|
"battery_fleet_status_low": "Low",
|
||||||
"battery_fleet_status_soon": "Soon",
|
"battery_fleet_status_soon": "Soon",
|
||||||
"battery_fleet_status_ok": "Healthy",
|
"battery_fleet_status_ok": "Healthy",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Agrupar por",
|
"group_by_label": "Agrupar por",
|
||||||
"state_value_help": "Usa el valor de estado de HA (normalmente en minúsculas, p. ej. \"on\"/\"off\"). Las mayúsculas se normalizan al guardar.",
|
"state_value_help": "Usa el valor de estado de HA (normalmente en minúsculas, p. ej. \"on\"/\"off\"). Las mayúsculas se normalizan al guardar.",
|
||||||
"target_changes_help": "Número de transiciones coincidentes antes de que se dispare el trigger (predeterminado: 1).",
|
"target_changes_help": "Número de transiciones coincidentes antes de que se dispare el trigger (predeterminado: 1).",
|
||||||
|
"for_minutes_state_help": "0 cuenta cada cambio de inmediato. Con minutos, el nuevo estado debe mantenerse primero — un parpadeo breve entonces ni dispara ni cuenta.",
|
||||||
"qr_print_title": "Imprimir códigos QR",
|
"qr_print_title": "Imprimir códigos QR",
|
||||||
"qr_print_desc": "Genera una página imprimible de códigos QR para recortar y pegar en tus equipos.",
|
"qr_print_desc": "Genera una página imprimible de códigos QR para recortar y pegar en tus equipos.",
|
||||||
"qr_print_load": "Cargar objetos",
|
"qr_print_load": "Cargar objetos",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "No era necesario",
|
"quick_complete_defaults_feedback_not_needed": "No era necesario",
|
||||||
"quick_complete_success": "Completado rápido",
|
"quick_complete_success": "Completado rápido",
|
||||||
"trigger_replaced": "Disparador reemplazado",
|
"trigger_replaced": "Disparador reemplazado",
|
||||||
|
"trigger_removed": "Disparador eliminado",
|
||||||
"add": "Añadir",
|
"add": "Añadir",
|
||||||
"show_stats": "Mostrar estadísticas + gráficos",
|
"show_stats": "Mostrar estadísticas + gráficos",
|
||||||
"hide_stats": "Ocultar estadísticas",
|
"hide_stats": "Ocultar estadísticas",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Suponiendo finalización puntual.",
|
"schedule_preview_ontime": "Suponiendo finalización puntual.",
|
||||||
"schedule_preview_ends": "(la serie termina)",
|
"schedule_preview_ends": "(la serie termina)",
|
||||||
"adopt_problem_responsible": "Usuario responsable para todas las tareas adoptadas (opcional)",
|
"adopt_problem_responsible": "Usuario responsable para todas las tareas adoptadas (opcional)",
|
||||||
|
"adopt_for_minutes_hint": "Disparar solo si el problema persiste este tiempo — 0 reacciona al primer parpadeo.",
|
||||||
"adopt_problem_configure": "Configurar",
|
"adopt_problem_configure": "Configurar",
|
||||||
"history_auto": "Automático",
|
"history_auto": "Automático",
|
||||||
"battery_fleet_title": "Flota de baterías",
|
"battery_fleet_title": "Flota de baterías",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Excluye aquí un dispositivo antes de que llegue a avisar de carga baja — un aspirador que se recarga solo o un teléfono que ya avisa por su cuenta.",
|
"battery_fleet_all_hint": "Excluye aquí un dispositivo antes de que llegue a avisar de carga baja — un aspirador que se recarga solo o un teléfono que ya avisa por su cuenta.",
|
||||||
"battery_fleet_add": "Añadir una batería",
|
"battery_fleet_add": "Añadir una batería",
|
||||||
"battery_fleet_add_hint": "Elige un sensor de batería que la detección automática pasó por alto — se une a la lista de inmediato.",
|
"battery_fleet_add_hint": "Elige un sensor de batería que la detección automática pasó por alto — se une a la lista de inmediato.",
|
||||||
|
"battery_fleet_track_self": "Seguir baterías de carga automática",
|
||||||
|
"battery_fleet_track_self_hint": "Teléfonos, aspiradoras y otros dispositivos que se recargan solos aparecen como recargables: si están bajos piden una carga, nunca pilas nuevas.",
|
||||||
"battery_fleet_status_low": "Baja",
|
"battery_fleet_status_low": "Baja",
|
||||||
"battery_fleet_status_soon": "Pronto",
|
"battery_fleet_status_soon": "Pronto",
|
||||||
"battery_fleet_status_ok": "Correcta",
|
"battery_fleet_status_ok": "Correcta",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Pian erääntyy",
|
"due_soon": "Pian erääntyy",
|
||||||
"triggered": "Laukaistu",
|
"triggered": "Laukaistu",
|
||||||
"trigger_replaced": "Laukaisin korvattu",
|
"trigger_replaced": "Laukaisin korvattu",
|
||||||
|
"trigger_removed": "Laukaisin poistettu",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Kaikki",
|
"all": "Kaikki",
|
||||||
"new_object": "+ Uusi kohde",
|
"new_object": "+ Uusi kohde",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Ryhmittele",
|
"group_by_label": "Ryhmittele",
|
||||||
"state_value_help": "Käytä HA:n tila-arvoa (yleensä pienillä kirjaimilla, esim. \"on\"/\"off\"). Kirjainkoko normalisoidaan tallennettaessa.",
|
"state_value_help": "Käytä HA:n tila-arvoa (yleensä pienillä kirjaimilla, esim. \"on\"/\"off\"). Kirjainkoko normalisoidaan tallennettaessa.",
|
||||||
"target_changes_help": "Vastaavien siirtymien määrä ennen laukaisua (oletus: 1).",
|
"target_changes_help": "Vastaavien siirtymien määrä ennen laukaisua (oletus: 1).",
|
||||||
|
"for_minutes_state_help": "0 laskee jokaisen muutoksen heti. Minuuteilla uuden tilan on ensin pysyttävä — lyhyet välähdykset eivät silloin laukaise eivätkä kirjaudu.",
|
||||||
"qr_print_title": "Tulosta QR-koodit",
|
"qr_print_title": "Tulosta QR-koodit",
|
||||||
"qr_print_desc": "Luo tulostettava sivu QR-koodeja, jotka voit leikata ja kiinnittää laitteisiin.",
|
"qr_print_desc": "Luo tulostettava sivu QR-koodeja, jotka voit leikata ja kiinnittää laitteisiin.",
|
||||||
"qr_print_load": "Lataa kohteet",
|
"qr_print_load": "Lataa kohteet",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Olettaen, että valmistuminen on ajallaan.",
|
"schedule_preview_ontime": "Olettaen, että valmistuminen on ajallaan.",
|
||||||
"schedule_preview_ends": "(sarja päättyy)",
|
"schedule_preview_ends": "(sarja päättyy)",
|
||||||
"adopt_problem_responsible": "Vastuuhenkilö kaikille omaksutuille tehtäville (valinnainen)",
|
"adopt_problem_responsible": "Vastuuhenkilö kaikille omaksutuille tehtäville (valinnainen)",
|
||||||
|
"adopt_for_minutes_hint": "Laukaise vasta kun ongelma on kestänyt näin kauan — 0 reagoi ensimmäiseen välähdykseen.",
|
||||||
"adopt_problem_configure": "Määritä",
|
"adopt_problem_configure": "Määritä",
|
||||||
"history_auto": "Automaattinen",
|
"history_auto": "Automaattinen",
|
||||||
"battery_fleet_title": "Paristokanta",
|
"battery_fleet_title": "Paristokanta",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Sulje laite pois jo ennen kuin se ilmoittaa alhaisesta varauksesta — imuri, joka lataa itsensä, tai puhelin, joka varoittaa itse.",
|
"battery_fleet_all_hint": "Sulje laite pois jo ennen kuin se ilmoittaa alhaisesta varauksesta — imuri, joka lataa itsensä, tai puhelin, joka varoittaa itse.",
|
||||||
"battery_fleet_add": "Lisää paristo",
|
"battery_fleet_add": "Lisää paristo",
|
||||||
"battery_fleet_add_hint": "Valitse paristoanturi, jonka automaattinen tunnistus ohitti — se liittyy luetteloon heti.",
|
"battery_fleet_add_hint": "Valitse paristoanturi, jonka automaattinen tunnistus ohitti — se liittyy luetteloon heti.",
|
||||||
|
"battery_fleet_track_self": "Seuraa itselatautuvia akkuja",
|
||||||
|
"battery_fleet_track_self_hint": "Puhelimet, imurit ja muut itselatautuvat laitteet näkyvät ladattavina — vähissä oleva pyytää latausta, ei koskaan uusia paristoja.",
|
||||||
"battery_fleet_status_low": "Vähissä",
|
"battery_fleet_status_low": "Vähissä",
|
||||||
"battery_fleet_status_soon": "Pian",
|
"battery_fleet_status_soon": "Pian",
|
||||||
"battery_fleet_status_ok": "Kunnossa",
|
"battery_fleet_status_ok": "Kunnossa",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Grouper par",
|
"group_by_label": "Grouper par",
|
||||||
"state_value_help": "Utilisez la valeur d'état HA (généralement en minuscules, p. ex. \"on\"/\"off\"). La casse est normalisée à l'enregistrement.",
|
"state_value_help": "Utilisez la valeur d'état HA (généralement en minuscules, p. ex. \"on\"/\"off\"). La casse est normalisée à l'enregistrement.",
|
||||||
"target_changes_help": "Nombre de transitions correspondantes avant le déclenchement (par défaut : 1).",
|
"target_changes_help": "Nombre de transitions correspondantes avant le déclenchement (par défaut : 1).",
|
||||||
|
"for_minutes_state_help": "0 compte chaque changement immédiatement. Avec des minutes, le nouvel état doit d'abord se maintenir — un bref clignotement ne déclenche ni ne compte alors.",
|
||||||
"qr_print_title": "Imprimer les QR codes",
|
"qr_print_title": "Imprimer les QR codes",
|
||||||
"qr_print_desc": "Générer une page imprimable de QR codes à découper et coller sur votre équipement.",
|
"qr_print_desc": "Générer une page imprimable de QR codes à découper et coller sur votre équipement.",
|
||||||
"qr_print_load": "Charger les objets",
|
"qr_print_load": "Charger les objets",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Non nécessaire",
|
"quick_complete_defaults_feedback_not_needed": "Non nécessaire",
|
||||||
"quick_complete_success": "Terminé rapidement",
|
"quick_complete_success": "Terminé rapidement",
|
||||||
"trigger_replaced": "Déclencheur remplacé",
|
"trigger_replaced": "Déclencheur remplacé",
|
||||||
|
"trigger_removed": "Déclencheur supprimé",
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
"show_stats": "Afficher les statistiques + graphiques",
|
"show_stats": "Afficher les statistiques + graphiques",
|
||||||
"hide_stats": "Masquer les statistiques",
|
"hide_stats": "Masquer les statistiques",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "En supposant un achèvement ponctuel.",
|
"schedule_preview_ontime": "En supposant un achèvement ponctuel.",
|
||||||
"schedule_preview_ends": "(fin de la série)",
|
"schedule_preview_ends": "(fin de la série)",
|
||||||
"adopt_problem_responsible": "Personne responsable pour toutes les tâches adoptées (optionnel)",
|
"adopt_problem_responsible": "Personne responsable pour toutes les tâches adoptées (optionnel)",
|
||||||
|
"adopt_for_minutes_hint": "Ne déclencher que si le problème persiste aussi longtemps — 0 réagit au premier clignotement.",
|
||||||
"adopt_problem_configure": "Configurer",
|
"adopt_problem_configure": "Configurer",
|
||||||
"history_auto": "Automatique",
|
"history_auto": "Automatique",
|
||||||
"battery_fleet_title": "Parc de piles",
|
"battery_fleet_title": "Parc de piles",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Excluez un appareil ici avant même qu'il ne signale une charge faible — un aspirateur qui se recharge seul, ou un téléphone qui vous prévient lui-même.",
|
"battery_fleet_all_hint": "Excluez un appareil ici avant même qu'il ne signale une charge faible — un aspirateur qui se recharge seul, ou un téléphone qui vous prévient lui-même.",
|
||||||
"battery_fleet_add": "Ajouter une batterie",
|
"battery_fleet_add": "Ajouter une batterie",
|
||||||
"battery_fleet_add_hint": "Choisissez un capteur de batterie que la détection automatique a manqué — il rejoint la liste immédiatement.",
|
"battery_fleet_add_hint": "Choisissez un capteur de batterie que la détection automatique a manqué — il rejoint la liste immédiatement.",
|
||||||
|
"battery_fleet_track_self": "Suivre les batteries à recharge automatique",
|
||||||
|
"battery_fleet_track_self_hint": "Les téléphones, aspirateurs et autres appareils qui se rechargent seuls apparaissent comme rechargeables — un niveau bas demande une recharge, jamais de nouvelles piles.",
|
||||||
"battery_fleet_status_low": "Faible",
|
"battery_fleet_status_low": "Faible",
|
||||||
"battery_fleet_status_soon": "Bientôt",
|
"battery_fleet_status_soon": "Bientôt",
|
||||||
"battery_fleet_status_ok": "Bon état",
|
"battery_fleet_status_ok": "Bon état",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "जल्द देय",
|
"due_soon": "जल्द देय",
|
||||||
"triggered": "ट्रिगर हुआ",
|
"triggered": "ट्रिगर हुआ",
|
||||||
"trigger_replaced": "ट्रिगर बदला गया",
|
"trigger_replaced": "ट्रिगर बदला गया",
|
||||||
|
"trigger_removed": "ट्रिगर हटाया गया",
|
||||||
"ok": "ठीक",
|
"ok": "ठीक",
|
||||||
"all": "सभी",
|
"all": "सभी",
|
||||||
"new_object": "+ नई वस्तु",
|
"new_object": "+ नई वस्तु",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "इसके अनुसार समूहित करें",
|
"group_by_label": "इसके अनुसार समूहित करें",
|
||||||
"state_value_help": "HA स्थिति मान का उपयोग करें (आमतौर पर लोअरकेस, उदा. \"on\"/\"off\")। सहेजने पर केस सामान्यीकृत हो जाता है।",
|
"state_value_help": "HA स्थिति मान का उपयोग करें (आमतौर पर लोअरकेस, उदा. \"on\"/\"off\")। सहेजने पर केस सामान्यीकृत हो जाता है।",
|
||||||
"target_changes_help": "ट्रिगर होने से पहले मिलान वाले संक्रमणों की संख्या (डिफ़ॉल्ट: 1)।",
|
"target_changes_help": "ट्रिगर होने से पहले मिलान वाले संक्रमणों की संख्या (डिफ़ॉल्ट: 1)।",
|
||||||
|
"for_minutes_state_help": "0 हर परिवर्तन को तुरंत गिनता है। मिनट सेट करने पर नई स्थिति को पहले उतनी देर बनी रहनी होगी — क्षणिक झिलमिलाहट तब न ट्रिगर करती है न गिनी जाती है।",
|
||||||
"qr_print_title": "QR कोड प्रिंट करें",
|
"qr_print_title": "QR कोड प्रिंट करें",
|
||||||
"qr_print_desc": "अपने उपकरण पर काटकर चिपकाने के लिए QR कोड का प्रिंट-योग्य पृष्ठ बनाएँ।",
|
"qr_print_desc": "अपने उपकरण पर काटकर चिपकाने के लिए QR कोड का प्रिंट-योग्य पृष्ठ बनाएँ।",
|
||||||
"qr_print_load": "वस्तुएँ लोड करें",
|
"qr_print_load": "वस्तुएँ लोड करें",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "समय पर पूर्णता मानते हुए।",
|
"schedule_preview_ontime": "समय पर पूर्णता मानते हुए।",
|
||||||
"schedule_preview_ends": "(श्रृंखला समाप्त)",
|
"schedule_preview_ends": "(श्रृंखला समाप्त)",
|
||||||
"adopt_problem_responsible": "सभी अपनाए गए कार्यों के लिए ज़िम्मेदार उपयोगकर्ता (वैकल्पिक)",
|
"adopt_problem_responsible": "सभी अपनाए गए कार्यों के लिए ज़िम्मेदार उपयोगकर्ता (वैकल्पिक)",
|
||||||
|
"adopt_for_minutes_hint": "समस्या इतनी देर बनी रहने पर ही ट्रिगर करें — 0 पहली झिलमिलाहट पर प्रतिक्रिया करता है।",
|
||||||
"adopt_problem_configure": "कॉन्फ़िगर करें",
|
"adopt_problem_configure": "कॉन्फ़िगर करें",
|
||||||
"history_auto": "स्वचालित",
|
"history_auto": "स्वचालित",
|
||||||
"battery_fleet_title": "बैटरी फ्लीट",
|
"battery_fleet_title": "बैटरी फ्लीट",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "किसी डिवाइस को यहाँ कम बैटरी बताने से पहले ही बाहर रखें — जैसे खुद चार्ज होने वाला वैक्यूम, या खुद ही चेतावनी देने वाला फ़ोन।",
|
"battery_fleet_all_hint": "किसी डिवाइस को यहाँ कम बैटरी बताने से पहले ही बाहर रखें — जैसे खुद चार्ज होने वाला वैक्यूम, या खुद ही चेतावनी देने वाला फ़ोन।",
|
||||||
"battery_fleet_add": "बैटरी जोड़ें",
|
"battery_fleet_add": "बैटरी जोड़ें",
|
||||||
"battery_fleet_add_hint": "स्वचालित खोज से छूटा बैटरी सेंसर चुनें — यह तुरंत सूची में जुड़ जाएगा।",
|
"battery_fleet_add_hint": "स्वचालित खोज से छूटा बैटरी सेंसर चुनें — यह तुरंत सूची में जुड़ जाएगा।",
|
||||||
|
"battery_fleet_track_self": "स्वयं चार्ज होने वाली बैटरियों को ट्रैक करें",
|
||||||
|
"battery_fleet_track_self_hint": "फ़ोन, वैक्यूम और अन्य स्वयं चार्ज होने वाले उपकरण रिचार्जेबल के रूप में दिखते हैं — कम होने पर चार्ज करने को कहा जाता है, नई बैटरी कभी नहीं।",
|
||||||
"battery_fleet_status_low": "कम",
|
"battery_fleet_status_low": "कम",
|
||||||
"battery_fleet_status_soon": "जल्द",
|
"battery_fleet_status_soon": "जल्द",
|
||||||
"battery_fleet_status_ok": "ठीक",
|
"battery_fleet_status_ok": "ठीक",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Hamarosan esedékes",
|
"due_soon": "Hamarosan esedékes",
|
||||||
"triggered": "Aktiválva",
|
"triggered": "Aktiválva",
|
||||||
"trigger_replaced": "Kiváltó lecserélve",
|
"trigger_replaced": "Kiváltó lecserélve",
|
||||||
|
"trigger_removed": "Eseményindító eltávolítva",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Összes",
|
"all": "Összes",
|
||||||
"new_object": "+ Új objektum",
|
"new_object": "+ Új objektum",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Csoportosítás",
|
"group_by_label": "Csoportosítás",
|
||||||
"state_value_help": "A HA állapotértékét használja (általában kisbetűs, pl. „on”/„off”). A kis- és nagybetűk mentéskor normalizálódnak.",
|
"state_value_help": "A HA állapotértékét használja (általában kisbetűs, pl. „on”/„off”). A kis- és nagybetűk mentéskor normalizálódnak.",
|
||||||
"target_changes_help": "Az egyező átmenetek száma a kiváltás előtt (alapértelmezés: 1).",
|
"target_changes_help": "Az egyező átmenetek száma a kiváltás előtt (alapértelmezés: 1).",
|
||||||
|
"for_minutes_state_help": "A 0 minden változást azonnal számol. Percekkel az új állapotnak előbb fenn kell maradnia — a rövid villanás ekkor se nem indít, se nem számít.",
|
||||||
"qr_print_title": "QR-kódok nyomtatása",
|
"qr_print_title": "QR-kódok nyomtatása",
|
||||||
"qr_print_desc": "Nyomtatható oldal QR-kódokkal — vágja ki és ragassza fel a berendezésekre.",
|
"qr_print_desc": "Nyomtatható oldal QR-kódokkal — vágja ki és ragassza fel a berendezésekre.",
|
||||||
"qr_print_load": "Objektumok betöltése",
|
"qr_print_load": "Objektumok betöltése",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Időben történő elvégzést feltételezve.",
|
"schedule_preview_ontime": "Időben történő elvégzést feltételezve.",
|
||||||
"schedule_preview_ends": "(a sorozat véget ér)",
|
"schedule_preview_ends": "(a sorozat véget ér)",
|
||||||
"adopt_problem_responsible": "Felelős felhasználó az összes átvett feladathoz (opcionális)",
|
"adopt_problem_responsible": "Felelős felhasználó az összes átvett feladathoz (opcionális)",
|
||||||
|
"adopt_for_minutes_hint": "Csak akkor induljon, ha a probléma ennyi ideig fennáll — a 0 az első villanásra reagál.",
|
||||||
"adopt_problem_configure": "Beállítás",
|
"adopt_problem_configure": "Beállítás",
|
||||||
"history_auto": "Automatikus",
|
"history_auto": "Automatikus",
|
||||||
"battery_fleet_title": "Elemflotta",
|
"battery_fleet_title": "Elemflotta",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Zárjon ki egy eszközt már azelőtt, hogy alacsony töltöttséget jelezne — például egy magát töltő porszívót vagy egy telefont, amely magától figyelmeztet.",
|
"battery_fleet_all_hint": "Zárjon ki egy eszközt már azelőtt, hogy alacsony töltöttséget jelezne — például egy magát töltő porszívót vagy egy telefont, amely magától figyelmeztet.",
|
||||||
"battery_fleet_add": "Elem hozzáadása",
|
"battery_fleet_add": "Elem hozzáadása",
|
||||||
"battery_fleet_add_hint": "Válassz egy elem-érzékelőt, amelyet az automatikus felismerés kihagyott — azonnal felkerül a listára.",
|
"battery_fleet_add_hint": "Válassz egy elem-érzékelőt, amelyet az automatikus felismerés kihagyott — azonnal felkerül a listára.",
|
||||||
|
"battery_fleet_track_self": "Öntöltő akkumulátorok követése",
|
||||||
|
"battery_fleet_track_self_hint": "A telefonok, porszívók és más öntöltő eszközök akkumulátorként jelennek meg — alacsony töltöttségnél töltést kérnek, sosem új elemet.",
|
||||||
"battery_fleet_status_low": "Alacsony",
|
"battery_fleet_status_low": "Alacsony",
|
||||||
"battery_fleet_status_soon": "Hamarosan",
|
"battery_fleet_status_soon": "Hamarosan",
|
||||||
"battery_fleet_status_ok": "Rendben",
|
"battery_fleet_status_ok": "Rendben",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Raggruppa per",
|
"group_by_label": "Raggruppa per",
|
||||||
"state_value_help": "Usa il valore di stato HA (di solito minuscolo, es. \"on\"/\"off\"). Il case viene normalizzato al salvataggio.",
|
"state_value_help": "Usa il valore di stato HA (di solito minuscolo, es. \"on\"/\"off\"). Il case viene normalizzato al salvataggio.",
|
||||||
"target_changes_help": "Numero di transizioni corrispondenti prima che il trigger si attivi (predefinito: 1).",
|
"target_changes_help": "Numero di transizioni corrispondenti prima che il trigger si attivi (predefinito: 1).",
|
||||||
|
"for_minutes_state_help": "0 conta ogni cambiamento subito. Con i minuti, il nuovo stato deve prima mantenersi — un breve sfarfallio allora non attiva né conta.",
|
||||||
"qr_print_title": "Stampa codici QR",
|
"qr_print_title": "Stampa codici QR",
|
||||||
"qr_print_desc": "Genera una pagina stampabile di codici QR da ritagliare e applicare sulle apparecchiature.",
|
"qr_print_desc": "Genera una pagina stampabile di codici QR da ritagliare e applicare sulle apparecchiature.",
|
||||||
"qr_print_load": "Carica oggetti",
|
"qr_print_load": "Carica oggetti",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Non necessario",
|
"quick_complete_defaults_feedback_not_needed": "Non necessario",
|
||||||
"quick_complete_success": "Completato rapidamente",
|
"quick_complete_success": "Completato rapidamente",
|
||||||
"trigger_replaced": "Trigger sostituito",
|
"trigger_replaced": "Trigger sostituito",
|
||||||
|
"trigger_removed": "Trigger rimosso",
|
||||||
"add": "Aggiungi",
|
"add": "Aggiungi",
|
||||||
"show_stats": "Mostra statistiche + grafici",
|
"show_stats": "Mostra statistiche + grafici",
|
||||||
"hide_stats": "Nascondi statistiche",
|
"hide_stats": "Nascondi statistiche",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Presumendo un completamento puntuale.",
|
"schedule_preview_ontime": "Presumendo un completamento puntuale.",
|
||||||
"schedule_preview_ends": "(la serie termina)",
|
"schedule_preview_ends": "(la serie termina)",
|
||||||
"adopt_problem_responsible": "Utente responsabile per tutte le attività adottate (opzionale)",
|
"adopt_problem_responsible": "Utente responsabile per tutte le attività adottate (opzionale)",
|
||||||
|
"adopt_for_minutes_hint": "Attiva solo se il problema persiste per questo tempo — 0 reagisce al primo sfarfallio.",
|
||||||
"adopt_problem_configure": "Configura",
|
"adopt_problem_configure": "Configura",
|
||||||
"history_auto": "Automatico",
|
"history_auto": "Automatico",
|
||||||
"battery_fleet_title": "Parco batterie",
|
"battery_fleet_title": "Parco batterie",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Escludi qui un dispositivo prima ancora che segnali la batteria scarica — un aspirapolvere che si ricarica da solo o un telefono che avvisa da sé.",
|
"battery_fleet_all_hint": "Escludi qui un dispositivo prima ancora che segnali la batteria scarica — un aspirapolvere che si ricarica da solo o un telefono che avvisa da sé.",
|
||||||
"battery_fleet_add": "Aggiungi una batteria",
|
"battery_fleet_add": "Aggiungi una batteria",
|
||||||
"battery_fleet_add_hint": "Scegli un sensore batteria sfuggito al rilevamento automatico — entra subito nell'elenco.",
|
"battery_fleet_add_hint": "Scegli un sensore batteria sfuggito al rilevamento automatico — entra subito nell'elenco.",
|
||||||
|
"battery_fleet_track_self": "Traccia le batterie autoricaricabili",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoni, aspirapolvere e altri dispositivi che si ricaricano da soli appaiono come ricaricabili — se scarichi chiedono una ricarica, mai batterie nuove.",
|
||||||
"battery_fleet_status_low": "Scarica",
|
"battery_fleet_status_low": "Scarica",
|
||||||
"battery_fleet_status_soon": "A breve",
|
"battery_fleet_status_soon": "A breve",
|
||||||
"battery_fleet_status_ok": "A posto",
|
"battery_fleet_status_ok": "A posto",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "期限間近",
|
"due_soon": "期限間近",
|
||||||
"triggered": "トリガー発生",
|
"triggered": "トリガー発生",
|
||||||
"trigger_replaced": "トリガー解除済み",
|
"trigger_replaced": "トリガー解除済み",
|
||||||
|
"trigger_removed": "トリガーを削除しました",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "すべて",
|
"all": "すべて",
|
||||||
"new_object": "+ 新規対象",
|
"new_object": "+ 新規対象",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "グループ化",
|
"group_by_label": "グループ化",
|
||||||
"state_value_help": "HAの状態値を使用してください(通常は小文字、例: \"on\"/\"off\")。保存時に大文字小文字は正規化されます。",
|
"state_value_help": "HAの状態値を使用してください(通常は小文字、例: \"on\"/\"off\")。保存時に大文字小文字は正規化されます。",
|
||||||
"target_changes_help": "トリガーが発生するまでに一致する遷移の回数(既定: 1)。",
|
"target_changes_help": "トリガーが発生するまでに一致する遷移の回数(既定: 1)。",
|
||||||
|
"for_minutes_state_help": "0 はすべての変化を即座にカウントします。分を設定すると新しい状態がその時間続く必要があり、一瞬の変動はトリガーもカウントもされません。",
|
||||||
"qr_print_title": "QRコードを印刷",
|
"qr_print_title": "QRコードを印刷",
|
||||||
"qr_print_desc": "切り取って機器に貼れるQRコードの印刷ページを生成します。",
|
"qr_print_desc": "切り取って機器に貼れるQRコードの印刷ページを生成します。",
|
||||||
"qr_print_load": "対象を読み込む",
|
"qr_print_load": "対象を読み込む",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "期日どおりに完了した場合。",
|
"schedule_preview_ontime": "期日どおりに完了した場合。",
|
||||||
"schedule_preview_ends": "(シリーズ終了)",
|
"schedule_preview_ends": "(シリーズ終了)",
|
||||||
"adopt_problem_responsible": "採用したすべてのタスクの担当ユーザー(任意)",
|
"adopt_problem_responsible": "採用したすべてのタスクの担当ユーザー(任意)",
|
||||||
|
"adopt_for_minutes_hint": "問題がこの時間続いた場合にのみトリガーします。0 は最初の変動に即反応します。",
|
||||||
"adopt_problem_configure": "設定",
|
"adopt_problem_configure": "設定",
|
||||||
"history_auto": "自動",
|
"history_auto": "自動",
|
||||||
"battery_fleet_title": "電池フリート",
|
"battery_fleet_title": "電池フリート",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "電池残量が少ないと報告される前に、ここで機器を除外できます — 自動充電するロボット掃除機や、自分で警告するスマートフォンなど。",
|
"battery_fleet_all_hint": "電池残量が少ないと報告される前に、ここで機器を除外できます — 自動充電するロボット掃除機や、自分で警告するスマートフォンなど。",
|
||||||
"battery_fleet_add": "電池を追加",
|
"battery_fleet_add": "電池を追加",
|
||||||
"battery_fleet_add_hint": "自動検出が見逃した電池センサーを選択すると、すぐに一覧に追加されます。",
|
"battery_fleet_add_hint": "自動検出が見逃した電池センサーを選択すると、すぐに一覧に追加されます。",
|
||||||
|
"battery_fleet_track_self": "自己充電式バッテリーを追跡",
|
||||||
|
"battery_fleet_track_self_hint": "スマートフォンや掃除機など自己充電するデバイスは充電式として表示されます。残量が少ないときは充電を促し、電池交換は求めません。",
|
||||||
"battery_fleet_status_low": "残量低下",
|
"battery_fleet_status_low": "残量低下",
|
||||||
"battery_fleet_status_soon": "まもなく",
|
"battery_fleet_status_soon": "まもなく",
|
||||||
"battery_fleet_status_ok": "良好",
|
"battery_fleet_status_ok": "良好",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "기한 임박",
|
"due_soon": "기한 임박",
|
||||||
"triggered": "트리거됨",
|
"triggered": "트리거됨",
|
||||||
"trigger_replaced": "트리거 교체됨",
|
"trigger_replaced": "트리거 교체됨",
|
||||||
|
"trigger_removed": "트리거 제거됨",
|
||||||
"ok": "정상",
|
"ok": "정상",
|
||||||
"all": "전체",
|
"all": "전체",
|
||||||
"new_object": "+ 새 객체",
|
"new_object": "+ 새 객체",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "그룹화",
|
"group_by_label": "그룹화",
|
||||||
"state_value_help": "HA 상태 값을 사용하세요(보통 소문자, 예: \"on\"/\"off\"). 대소문자는 저장 시 정규화됩니다.",
|
"state_value_help": "HA 상태 값을 사용하세요(보통 소문자, 예: \"on\"/\"off\"). 대소문자는 저장 시 정규화됩니다.",
|
||||||
"target_changes_help": "트리거가 발동하기까지 필요한 일치 전환 횟수입니다 (기본값: 1).",
|
"target_changes_help": "트리거가 발동하기까지 필요한 일치 전환 횟수입니다 (기본값: 1).",
|
||||||
|
"for_minutes_state_help": "0은 모든 변화를 즉시 집계합니다. 분을 설정하면 새 상태가 먼저 그 시간 동안 유지되어야 하며, 짧은 깜빡임은 트리거되지도 집계되지도 않습니다.",
|
||||||
"qr_print_title": "QR 코드 인쇄",
|
"qr_print_title": "QR 코드 인쇄",
|
||||||
"qr_print_desc": "잘라서 장비에 붙일 수 있는 QR 코드 인쇄 페이지를 생성합니다.",
|
"qr_print_desc": "잘라서 장비에 붙일 수 있는 QR 코드 인쇄 페이지를 생성합니다.",
|
||||||
"qr_print_load": "객체 불러오기",
|
"qr_print_load": "객체 불러오기",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "제때 완료한다고 가정한 일정입니다.",
|
"schedule_preview_ontime": "제때 완료한다고 가정한 일정입니다.",
|
||||||
"schedule_preview_ends": "(시리즈 종료)",
|
"schedule_preview_ends": "(시리즈 종료)",
|
||||||
"adopt_problem_responsible": "가져온 모든 작업의 담당자 (선택)",
|
"adopt_problem_responsible": "가져온 모든 작업의 담당자 (선택)",
|
||||||
|
"adopt_for_minutes_hint": "문제가 이 시간 동안 지속된 경우에만 트리거합니다. 0은 첫 깜빡임에 즉시 반응합니다.",
|
||||||
"adopt_problem_configure": "구성",
|
"adopt_problem_configure": "구성",
|
||||||
"history_auto": "자동",
|
"history_auto": "자동",
|
||||||
"battery_fleet_title": "배터리 플릿",
|
"battery_fleet_title": "배터리 플릿",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "배터리 부족을 알리기 전에 여기에서 기기를 제외하세요 — 스스로 충전하는 로봇청소기나 자체적으로 경고하는 휴대폰처럼.",
|
"battery_fleet_all_hint": "배터리 부족을 알리기 전에 여기에서 기기를 제외하세요 — 스스로 충전하는 로봇청소기나 자체적으로 경고하는 휴대폰처럼.",
|
||||||
"battery_fleet_add": "배터리 추가",
|
"battery_fleet_add": "배터리 추가",
|
||||||
"battery_fleet_add_hint": "자동 검색이 놓친 배터리 센서를 선택하면 즉시 목록에 추가됩니다.",
|
"battery_fleet_add_hint": "자동 검색이 놓친 배터리 센서를 선택하면 즉시 목록에 추가됩니다.",
|
||||||
|
"battery_fleet_track_self": "자체 충전 배터리 추적",
|
||||||
|
"battery_fleet_track_self_hint": "휴대폰, 청소기 등 스스로 충전되는 기기는 충전식으로 표시됩니다. 잔량이 낮으면 충전을 요청하며 새 전지를 요구하지 않습니다.",
|
||||||
"battery_fleet_status_low": "부족",
|
"battery_fleet_status_low": "부족",
|
||||||
"battery_fleet_status_soon": "곧",
|
"battery_fleet_status_soon": "곧",
|
||||||
"battery_fleet_status_ok": "정상",
|
"battery_fleet_status_ok": "정상",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Forfaller snart",
|
"due_soon": "Forfaller snart",
|
||||||
"triggered": "Utløst",
|
"triggered": "Utløst",
|
||||||
"trigger_replaced": "Utløser erstattet",
|
"trigger_replaced": "Utløser erstattet",
|
||||||
|
"trigger_removed": "Utløser fjernet",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"new_object": "+ Nytt objekt",
|
"new_object": "+ Nytt objekt",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "Grupper etter",
|
"group_by_label": "Grupper etter",
|
||||||
"state_value_help": "Bruk HA-tilstandsverdien (vanligvis små bokstaver, f.eks. \"on\"/\"off\"). Store/små bokstaver normaliseres ved lagring.",
|
"state_value_help": "Bruk HA-tilstandsverdien (vanligvis små bokstaver, f.eks. \"on\"/\"off\"). Store/små bokstaver normaliseres ved lagring.",
|
||||||
"target_changes_help": "Antall samsvarende overganger før utløseren aktiveres (standard: 1).",
|
"target_changes_help": "Antall samsvarende overganger før utløseren aktiveres (standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 teller hver endring umiddelbart. Med minutter må den nye tilstanden først holde seg — korte blink verken utløser eller telles da.",
|
||||||
"qr_print_title": "Skriv ut QR-koder",
|
"qr_print_title": "Skriv ut QR-koder",
|
||||||
"qr_print_desc": "Generer en utskrivbar side med QR-koder som kan klippes ut og festes på utstyret ditt.",
|
"qr_print_desc": "Generer en utskrivbar side med QR-koder som kan klippes ut og festes på utstyret ditt.",
|
||||||
"qr_print_load": "Last objekter",
|
"qr_print_load": "Last objekter",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Forutsatt fullføring i tide.",
|
"schedule_preview_ontime": "Forutsatt fullføring i tide.",
|
||||||
"schedule_preview_ends": "(serien avsluttes)",
|
"schedule_preview_ends": "(serien avsluttes)",
|
||||||
"adopt_problem_responsible": "Ansvarlig bruker for alle overtatte oppgaver (valgfritt)",
|
"adopt_problem_responsible": "Ansvarlig bruker for alle overtatte oppgaver (valgfritt)",
|
||||||
|
"adopt_for_minutes_hint": "Utløs først når problemet har vart så lenge — 0 reagerer på første blink.",
|
||||||
"adopt_problem_configure": "Konfigurer",
|
"adopt_problem_configure": "Konfigurer",
|
||||||
"history_auto": "Automatisk",
|
"history_auto": "Automatisk",
|
||||||
"battery_fleet_title": "Batteriflåte",
|
"battery_fleet_title": "Batteriflåte",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Utelukk en enhet her før den i det hele tatt melder lavt nivå — en støvsuger som lader seg selv, eller en telefon som varsler av seg selv.",
|
"battery_fleet_all_hint": "Utelukk en enhet her før den i det hele tatt melder lavt nivå — en støvsuger som lader seg selv, eller en telefon som varsler av seg selv.",
|
||||||
"battery_fleet_add": "Legg til et batteri",
|
"battery_fleet_add": "Legg til et batteri",
|
||||||
"battery_fleet_add_hint": "Velg en batterisensor som den automatiske gjenkjenningen overså — den blir med i listen umiddelbart.",
|
"battery_fleet_add_hint": "Velg en batterisensor som den automatiske gjenkjenningen overså — den blir med i listen umiddelbart.",
|
||||||
|
"battery_fleet_track_self": "Spor selvladende batterier",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoner, støvsugere og andre selvladende enheter vises som oppladbare — ved lavt nivå bes det om lading, aldri om nye batterier.",
|
||||||
"battery_fleet_status_low": "Lav",
|
"battery_fleet_status_low": "Lav",
|
||||||
"battery_fleet_status_soon": "Snart",
|
"battery_fleet_status_soon": "Snart",
|
||||||
"battery_fleet_status_ok": "I orden",
|
"battery_fleet_status_ok": "I orden",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Groeperen op",
|
"group_by_label": "Groeperen op",
|
||||||
"state_value_help": "Gebruik de HA-statuswaarde (meestal in kleine letters, bv. \"on\"/\"off\"). Hoofdletters worden bij opslaan genormaliseerd.",
|
"state_value_help": "Gebruik de HA-statuswaarde (meestal in kleine letters, bv. \"on\"/\"off\"). Hoofdletters worden bij opslaan genormaliseerd.",
|
||||||
"target_changes_help": "Aantal overeenkomende overgangen voordat de trigger wordt geactiveerd (standaard: 1).",
|
"target_changes_help": "Aantal overeenkomende overgangen voordat de trigger wordt geactiveerd (standaard: 1).",
|
||||||
|
"for_minutes_state_help": "0 telt elke wijziging direct. Met minuten moet de nieuwe status eerst zo lang aanhouden — kort flikkeren triggert dan niet en telt niet mee.",
|
||||||
"qr_print_title": "QR-codes afdrukken",
|
"qr_print_title": "QR-codes afdrukken",
|
||||||
"qr_print_desc": "Genereer een afdrukpagina met QR-codes om uit te knippen en op je apparaten te plakken.",
|
"qr_print_desc": "Genereer een afdrukpagina met QR-codes om uit te knippen en op je apparaten te plakken.",
|
||||||
"qr_print_load": "Objecten laden",
|
"qr_print_load": "Objecten laden",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Niet nodig",
|
"quick_complete_defaults_feedback_not_needed": "Niet nodig",
|
||||||
"quick_complete_success": "Snel als voltooid gemarkeerd",
|
"quick_complete_success": "Snel als voltooid gemarkeerd",
|
||||||
"trigger_replaced": "Trigger vervangen",
|
"trigger_replaced": "Trigger vervangen",
|
||||||
|
"trigger_removed": "Trigger verwijderd",
|
||||||
"add": "Toevoegen",
|
"add": "Toevoegen",
|
||||||
"show_stats": "Statistieken + grafieken tonen",
|
"show_stats": "Statistieken + grafieken tonen",
|
||||||
"hide_stats": "Statistieken verbergen",
|
"hide_stats": "Statistieken verbergen",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Uitgaande van tijdige afronding.",
|
"schedule_preview_ontime": "Uitgaande van tijdige afronding.",
|
||||||
"schedule_preview_ends": "(reeks eindigt)",
|
"schedule_preview_ends": "(reeks eindigt)",
|
||||||
"adopt_problem_responsible": "Verantwoordelijke gebruiker voor alle overgenomen taken (optioneel)",
|
"adopt_problem_responsible": "Verantwoordelijke gebruiker voor alle overgenomen taken (optioneel)",
|
||||||
|
"adopt_for_minutes_hint": "Pas triggeren als het probleem zo lang aanhoudt — 0 reageert op de eerste flikkering.",
|
||||||
"adopt_problem_configure": "Configureren",
|
"adopt_problem_configure": "Configureren",
|
||||||
"history_auto": "Automatisch",
|
"history_auto": "Automatisch",
|
||||||
"battery_fleet_title": "Batterijvloot",
|
"battery_fleet_title": "Batterijvloot",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Sluit een apparaat hier uit voordat het ooit laag meldt — een stofzuiger die zichzelf oplaadt, of een telefoon die zelf waarschuwt.",
|
"battery_fleet_all_hint": "Sluit een apparaat hier uit voordat het ooit laag meldt — een stofzuiger die zichzelf oplaadt, of een telefoon die zelf waarschuwt.",
|
||||||
"battery_fleet_add": "Batterij toevoegen",
|
"battery_fleet_add": "Batterij toevoegen",
|
||||||
"battery_fleet_add_hint": "Kies een batterijsensor die de automatische detectie heeft gemist — hij verschijnt direct in de lijst.",
|
"battery_fleet_add_hint": "Kies een batterijsensor die de automatische detectie heeft gemist — hij verschijnt direct in de lijst.",
|
||||||
|
"battery_fleet_track_self": "Zelfopladende batterijen volgen",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoons, stofzuigers en andere zelfopladende apparaten verschijnen als oplaadbaar — bij een lage stand wordt om opladen gevraagd, nooit om nieuwe batterijen.",
|
||||||
"battery_fleet_status_low": "Laag",
|
"battery_fleet_status_low": "Laag",
|
||||||
"battery_fleet_status_soon": "Binnenkort",
|
"battery_fleet_status_soon": "Binnenkort",
|
||||||
"battery_fleet_status_ok": "In orde",
|
"battery_fleet_status_ok": "In orde",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Grupuj wg",
|
"group_by_label": "Grupuj wg",
|
||||||
"state_value_help": "Użyj wartości stanu HA (zwykle małymi literami, np. \"on\"/\"off\"). Wielkość liter jest normalizowana przy zapisie.",
|
"state_value_help": "Użyj wartości stanu HA (zwykle małymi literami, np. \"on\"/\"off\"). Wielkość liter jest normalizowana przy zapisie.",
|
||||||
"target_changes_help": "Liczba pasujących przejść przed wyzwoleniem (domyślnie: 1).",
|
"target_changes_help": "Liczba pasujących przejść przed wyzwoleniem (domyślnie: 1).",
|
||||||
|
"for_minutes_state_help": "0 liczy każdą zmianę od razu. Z minutami nowy stan musi się najpierw utrzymać — krótkie mignięcia wtedy nie wyzwalają i nie liczą się.",
|
||||||
"qr_print_title": "Drukuj kody QR",
|
"qr_print_title": "Drukuj kody QR",
|
||||||
"qr_print_desc": "Wygeneruj stronę do druku z kodami QR do wycięcia i naklejenia na sprzęcie.",
|
"qr_print_desc": "Wygeneruj stronę do druku z kodami QR do wycięcia i naklejenia na sprzęcie.",
|
||||||
"qr_print_load": "Załaduj obiekty",
|
"qr_print_load": "Załaduj obiekty",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Nie było potrzebne",
|
"quick_complete_defaults_feedback_not_needed": "Nie było potrzebne",
|
||||||
"quick_complete_success": "Szybko oznaczono jako wykonane",
|
"quick_complete_success": "Szybko oznaczono jako wykonane",
|
||||||
"trigger_replaced": "Wyzwalacz zastąpiony",
|
"trigger_replaced": "Wyzwalacz zastąpiony",
|
||||||
|
"trigger_removed": "Wyzwalacz usunięty",
|
||||||
"add": "Dodaj",
|
"add": "Dodaj",
|
||||||
"show_stats": "Pokaż statystyki + wykresy",
|
"show_stats": "Pokaż statystyki + wykresy",
|
||||||
"hide_stats": "Ukryj statystyki",
|
"hide_stats": "Ukryj statystyki",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Przy założeniu terminowego ukończenia.",
|
"schedule_preview_ontime": "Przy założeniu terminowego ukończenia.",
|
||||||
"schedule_preview_ends": "(seria się kończy)",
|
"schedule_preview_ends": "(seria się kończy)",
|
||||||
"adopt_problem_responsible": "Osoba odpowiedzialna za wszystkie przejęte zadania (opcjonalnie)",
|
"adopt_problem_responsible": "Osoba odpowiedzialna za wszystkie przejęte zadania (opcjonalnie)",
|
||||||
|
"adopt_for_minutes_hint": "Wyzwalaj dopiero, gdy problem utrzymuje się tak długo — 0 reaguje na pierwsze mignięcie.",
|
||||||
"adopt_problem_configure": "Konfiguruj",
|
"adopt_problem_configure": "Konfiguruj",
|
||||||
"history_auto": "Automatycznie",
|
"history_auto": "Automatycznie",
|
||||||
"battery_fleet_title": "Flota baterii",
|
"battery_fleet_title": "Flota baterii",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Wyklucz tutaj urządzenie, zanim w ogóle zgłosi niski poziom — odkurzacz, który sam się ładuje, albo telefon, który ostrzega samodzielnie.",
|
"battery_fleet_all_hint": "Wyklucz tutaj urządzenie, zanim w ogóle zgłosi niski poziom — odkurzacz, który sam się ładuje, albo telefon, który ostrzega samodzielnie.",
|
||||||
"battery_fleet_add": "Dodaj baterię",
|
"battery_fleet_add": "Dodaj baterię",
|
||||||
"battery_fleet_add_hint": "Wybierz czujnik baterii pominięty przez automatyczne wykrywanie — od razu trafi na listę.",
|
"battery_fleet_add_hint": "Wybierz czujnik baterii pominięty przez automatyczne wykrywanie — od razu trafi na listę.",
|
||||||
|
"battery_fleet_track_self": "Śledź baterie samoładujące",
|
||||||
|
"battery_fleet_track_self_hint": "Telefony, odkurzacze i inne samoładujące się urządzenia pojawiają się jako akumulatory — przy niskim poziomie proszą o ładowanie, nigdy o nowe baterie.",
|
||||||
"battery_fleet_status_low": "Niski",
|
"battery_fleet_status_low": "Niski",
|
||||||
"battery_fleet_status_soon": "Wkrótce",
|
"battery_fleet_status_soon": "Wkrótce",
|
||||||
"battery_fleet_status_ok": "W porządku",
|
"battery_fleet_status_ok": "W porządku",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Vence em breve",
|
"due_soon": "Vence em breve",
|
||||||
"triggered": "Acionada",
|
"triggered": "Acionada",
|
||||||
"trigger_replaced": "Gatilho substituído",
|
"trigger_replaced": "Gatilho substituído",
|
||||||
|
"trigger_removed": "Gatilho removido",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"all": "Todas",
|
"all": "Todas",
|
||||||
"new_object": "+ Novo objeto",
|
"new_object": "+ Novo objeto",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Agrupar por",
|
"group_by_label": "Agrupar por",
|
||||||
"state_value_help": "Use o valor de estado do HA (geralmente minúsculo, ex.: \"on\"/\"off\"). Maiúsculas e minúsculas são normalizadas ao salvar.",
|
"state_value_help": "Use o valor de estado do HA (geralmente minúsculo, ex.: \"on\"/\"off\"). Maiúsculas e minúsculas são normalizadas ao salvar.",
|
||||||
"target_changes_help": "Número de transições correspondentes antes de o gatilho disparar (padrão: 1).",
|
"target_changes_help": "Número de transições correspondentes antes de o gatilho disparar (padrão: 1).",
|
||||||
|
"for_minutes_state_help": "0 conta cada mudança imediatamente. Com minutos, o novo estado deve se manter primeiro — uma oscilação breve então não dispara nem conta.",
|
||||||
"qr_print_title": "Imprimir códigos QR",
|
"qr_print_title": "Imprimir códigos QR",
|
||||||
"qr_print_desc": "Gere uma página imprimível de códigos QR para recortar e colar nos seus equipamentos.",
|
"qr_print_desc": "Gere uma página imprimível de códigos QR para recortar e colar nos seus equipamentos.",
|
||||||
"qr_print_load": "Carregar objetos",
|
"qr_print_load": "Carregar objetos",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Supondo conclusão em dia.",
|
"schedule_preview_ontime": "Supondo conclusão em dia.",
|
||||||
"schedule_preview_ends": "(a série termina)",
|
"schedule_preview_ends": "(a série termina)",
|
||||||
"adopt_problem_responsible": "Usuário responsável por todas as tarefas adotadas (opcional)",
|
"adopt_problem_responsible": "Usuário responsável por todas as tarefas adotadas (opcional)",
|
||||||
|
"adopt_for_minutes_hint": "Disparar apenas se o problema persistir por esse tempo — 0 reage à primeira oscilação.",
|
||||||
"adopt_problem_configure": "Configurar",
|
"adopt_problem_configure": "Configurar",
|
||||||
"history_auto": "Automática",
|
"history_auto": "Automática",
|
||||||
"battery_fleet_title": "Frota de baterias",
|
"battery_fleet_title": "Frota de baterias",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes que ele sequer indique carga baixa — um aspirador que se recarrega sozinho ou um celular que já avisa sozinho.",
|
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes que ele sequer indique carga baixa — um aspirador que se recarrega sozinho ou um celular que já avisa sozinho.",
|
||||||
"battery_fleet_add": "Adicionar uma bateria",
|
"battery_fleet_add": "Adicionar uma bateria",
|
||||||
"battery_fleet_add_hint": "Escolha um sensor de bateria que a detecção automática não encontrou — ele entra na lista imediatamente.",
|
"battery_fleet_add_hint": "Escolha um sensor de bateria que a detecção automática não encontrou — ele entra na lista imediatamente.",
|
||||||
|
"battery_fleet_track_self": "Acompanhar baterias de carregamento automático",
|
||||||
|
"battery_fleet_track_self_hint": "Telefones, aspiradores e outros dispositivos que se recarregam sozinhos aparecem como recarregáveis — com carga baixa pedem uma recarga, nunca pilhas novas.",
|
||||||
"battery_fleet_status_low": "Fraca",
|
"battery_fleet_status_low": "Fraca",
|
||||||
"battery_fleet_status_soon": "Em breve",
|
"battery_fleet_status_soon": "Em breve",
|
||||||
"battery_fleet_status_ok": "Em bom estado",
|
"battery_fleet_status_ok": "Em bom estado",
|
||||||
|
|||||||
@@ -513,6 +513,7 @@
|
|||||||
"group_by_label": "Agrupar por",
|
"group_by_label": "Agrupar por",
|
||||||
"state_value_help": "Use o valor de estado HA (normalmente em minúsculas, p. ex. \"on\"/\"off\"). As maiúsculas são normalizadas ao guardar.",
|
"state_value_help": "Use o valor de estado HA (normalmente em minúsculas, p. ex. \"on\"/\"off\"). As maiúsculas são normalizadas ao guardar.",
|
||||||
"target_changes_help": "Número de transições correspondentes antes do trigger disparar (predefinido: 1).",
|
"target_changes_help": "Número de transições correspondentes antes do trigger disparar (predefinido: 1).",
|
||||||
|
"for_minutes_state_help": "0 conta cada mudança de imediato. Com minutos, o novo estado tem de se manter primeiro — uma oscilação breve então não dispara nem conta.",
|
||||||
"qr_print_title": "Imprimir códigos QR",
|
"qr_print_title": "Imprimir códigos QR",
|
||||||
"qr_print_desc": "Gera uma página imprimível de códigos QR para recortar e colar nos equipamentos.",
|
"qr_print_desc": "Gera uma página imprimível de códigos QR para recortar e colar nos equipamentos.",
|
||||||
"qr_print_load": "Carregar objetos",
|
"qr_print_load": "Carregar objetos",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Não era necessário",
|
"quick_complete_defaults_feedback_not_needed": "Não era necessário",
|
||||||
"quick_complete_success": "Concluído rapidamente",
|
"quick_complete_success": "Concluído rapidamente",
|
||||||
"trigger_replaced": "Acionador substituído",
|
"trigger_replaced": "Acionador substituído",
|
||||||
|
"trigger_removed": "Gatilho removido",
|
||||||
"add": "Adicionar",
|
"add": "Adicionar",
|
||||||
"show_stats": "Mostrar estatísticas + gráficos",
|
"show_stats": "Mostrar estatísticas + gráficos",
|
||||||
"hide_stats": "Ocultar estatísticas",
|
"hide_stats": "Ocultar estatísticas",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Assumindo conclusão pontual.",
|
"schedule_preview_ontime": "Assumindo conclusão pontual.",
|
||||||
"schedule_preview_ends": "(a série termina)",
|
"schedule_preview_ends": "(a série termina)",
|
||||||
"adopt_problem_responsible": "Utilizador responsável por todas as tarefas adotadas (opcional)",
|
"adopt_problem_responsible": "Utilizador responsável por todas as tarefas adotadas (opcional)",
|
||||||
|
"adopt_for_minutes_hint": "Disparar apenas se o problema persistir este tempo — 0 reage à primeira oscilação.",
|
||||||
"adopt_problem_configure": "Configurar",
|
"adopt_problem_configure": "Configurar",
|
||||||
"history_auto": "Automático",
|
"history_auto": "Automático",
|
||||||
"battery_fleet_title": "Frota de baterias",
|
"battery_fleet_title": "Frota de baterias",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes de ele sequer indicar carga fraca — um aspirador que se recarrega sozinho ou um telemóvel que já avisa por si.",
|
"battery_fleet_all_hint": "Exclua aqui um dispositivo antes de ele sequer indicar carga fraca — um aspirador que se recarrega sozinho ou um telemóvel que já avisa por si.",
|
||||||
"battery_fleet_add": "Adicionar uma bateria",
|
"battery_fleet_add": "Adicionar uma bateria",
|
||||||
"battery_fleet_add_hint": "Escolha um sensor de bateria que a deteção automática não encontrou — entra de imediato na lista.",
|
"battery_fleet_add_hint": "Escolha um sensor de bateria que a deteção automática não encontrou — entra de imediato na lista.",
|
||||||
|
"battery_fleet_track_self": "Acompanhar baterias de carregamento automático",
|
||||||
|
"battery_fleet_track_self_hint": "Telefones, aspiradores e outros dispositivos que se recarregam sozinhos aparecem como recarregáveis — com carga baixa pedem um carregamento, nunca pilhas novas.",
|
||||||
"battery_fleet_status_low": "Fraca",
|
"battery_fleet_status_low": "Fraca",
|
||||||
"battery_fleet_status_soon": "Em breve",
|
"battery_fleet_status_soon": "Em breve",
|
||||||
"battery_fleet_status_ok": "Em bom estado",
|
"battery_fleet_status_ok": "Em bom estado",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Группировать по",
|
"group_by_label": "Группировать по",
|
||||||
"state_value_help": "Используйте значение состояния HA (обычно в нижнем регистре, напр. \"on\"/\"off\"). Регистр нормализуется при сохранении.",
|
"state_value_help": "Используйте значение состояния HA (обычно в нижнем регистре, напр. \"on\"/\"off\"). Регистр нормализуется при сохранении.",
|
||||||
"target_changes_help": "Количество совпадающих переходов, после которых срабатывает триггер (по умолчанию: 1).",
|
"target_changes_help": "Количество совпадающих переходов, после которых срабатывает триггер (по умолчанию: 1).",
|
||||||
|
"for_minutes_state_help": "0 — каждое изменение засчитывается сразу. С минутами новое состояние сначала должно удержаться — короткие сбои тогда не срабатывают и не считаются.",
|
||||||
"qr_print_title": "Печать QR-кодов",
|
"qr_print_title": "Печать QR-кодов",
|
||||||
"qr_print_desc": "Создай страницу для печати с QR-кодами для вырезания и наклеивания на оборудование.",
|
"qr_print_desc": "Создай страницу для печати с QR-кодами для вырезания и наклеивания на оборудование.",
|
||||||
"qr_print_load": "Загрузить объекты",
|
"qr_print_load": "Загрузить объекты",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Не было необходимо",
|
"quick_complete_defaults_feedback_not_needed": "Не было необходимо",
|
||||||
"quick_complete_success": "Быстро отмечено как выполнено",
|
"quick_complete_success": "Быстро отмечено как выполнено",
|
||||||
"trigger_replaced": "Триггер заменён",
|
"trigger_replaced": "Триггер заменён",
|
||||||
|
"trigger_removed": "Триггер удалён",
|
||||||
"add": "Добавить",
|
"add": "Добавить",
|
||||||
"show_stats": "Показать статистику + графики",
|
"show_stats": "Показать статистику + графики",
|
||||||
"hide_stats": "Скрыть статистику",
|
"hide_stats": "Скрыть статистику",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "При условии своевременного выполнения.",
|
"schedule_preview_ontime": "При условии своевременного выполнения.",
|
||||||
"schedule_preview_ends": "(серия завершается)",
|
"schedule_preview_ends": "(серия завершается)",
|
||||||
"adopt_problem_responsible": "Ответственный пользователь для всех принятых задач (необязательно)",
|
"adopt_problem_responsible": "Ответственный пользователь для всех принятых задач (необязательно)",
|
||||||
|
"adopt_for_minutes_hint": "Срабатывать только если проблема сохраняется это время — 0 реагирует на первый сбой.",
|
||||||
"adopt_problem_configure": "Настроить",
|
"adopt_problem_configure": "Настроить",
|
||||||
"history_auto": "Автоматически",
|
"history_auto": "Автоматически",
|
||||||
"battery_fleet_title": "Парк батарей",
|
"battery_fleet_title": "Парк батарей",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Исключите устройство здесь ещё до того, как оно сообщит о низком заряде — робот-пылесос, который заряжается сам, или телефон, который предупреждает вас сам.",
|
"battery_fleet_all_hint": "Исключите устройство здесь ещё до того, как оно сообщит о низком заряде — робот-пылесос, который заряжается сам, или телефон, который предупреждает вас сам.",
|
||||||
"battery_fleet_add": "Добавить батарею",
|
"battery_fleet_add": "Добавить батарею",
|
||||||
"battery_fleet_add_hint": "Выберите датчик батареи, который автоматическое обнаружение пропустило — он сразу появится в списке.",
|
"battery_fleet_add_hint": "Выберите датчик батареи, который автоматическое обнаружение пропустило — он сразу появится в списке.",
|
||||||
|
"battery_fleet_track_self": "Отслеживать самозаряжающиеся батареи",
|
||||||
|
"battery_fleet_track_self_hint": "Телефоны, пылесосы и другие самозаряжающиеся устройства отображаются как аккумуляторы — при низком заряде нужна зарядка, а не новые батарейки.",
|
||||||
"battery_fleet_status_low": "Низкий",
|
"battery_fleet_status_low": "Низкий",
|
||||||
"battery_fleet_status_soon": "Скоро",
|
"battery_fleet_status_soon": "Скоро",
|
||||||
"battery_fleet_status_ok": "В норме",
|
"battery_fleet_status_ok": "В норме",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Gruppera efter",
|
"group_by_label": "Gruppera efter",
|
||||||
"state_value_help": "Använd HA-tillståndsvärdet (vanligtvis med små bokstäver, t.ex. \"on\"/\"off\"). Versaler normaliseras vid sparande.",
|
"state_value_help": "Använd HA-tillståndsvärdet (vanligtvis med små bokstäver, t.ex. \"on\"/\"off\"). Versaler normaliseras vid sparande.",
|
||||||
"target_changes_help": "Antal matchande övergångar innan utlösaren aktiveras (standard: 1).",
|
"target_changes_help": "Antal matchande övergångar innan utlösaren aktiveras (standard: 1).",
|
||||||
|
"for_minutes_state_help": "0 räknar varje ändring direkt. Med minuter måste det nya tillståndet först hålla i sig — korta blinkningar varken utlöser eller räknas då.",
|
||||||
"qr_print_title": "Skriv ut QR-koder",
|
"qr_print_title": "Skriv ut QR-koder",
|
||||||
"qr_print_desc": "Skapa en utskriftssida med QR-koder att klippa ut och sätta på din utrustning.",
|
"qr_print_desc": "Skapa en utskriftssida med QR-koder att klippa ut och sätta på din utrustning.",
|
||||||
"qr_print_load": "Ladda objekt",
|
"qr_print_load": "Ladda objekt",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Inte nödvändigt",
|
"quick_complete_defaults_feedback_not_needed": "Inte nödvändigt",
|
||||||
"quick_complete_success": "Snabbt markerad som klar",
|
"quick_complete_success": "Snabbt markerad som klar",
|
||||||
"trigger_replaced": "Utlösare ersatt",
|
"trigger_replaced": "Utlösare ersatt",
|
||||||
|
"trigger_removed": "Utlösare borttagen",
|
||||||
"add": "Lägg till",
|
"add": "Lägg till",
|
||||||
"show_stats": "Visa statistik + grafer",
|
"show_stats": "Visa statistik + grafer",
|
||||||
"hide_stats": "Dölj statistik",
|
"hide_stats": "Dölj statistik",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Förutsatt slutförande i tid.",
|
"schedule_preview_ontime": "Förutsatt slutförande i tid.",
|
||||||
"schedule_preview_ends": "(serien avslutas)",
|
"schedule_preview_ends": "(serien avslutas)",
|
||||||
"adopt_problem_responsible": "Ansvarig användare för alla övertagna uppgifter (valfritt)",
|
"adopt_problem_responsible": "Ansvarig användare för alla övertagna uppgifter (valfritt)",
|
||||||
|
"adopt_for_minutes_hint": "Utlös först när problemet kvarstått så här länge — 0 reagerar på första blinkningen.",
|
||||||
"adopt_problem_configure": "Konfigurera",
|
"adopt_problem_configure": "Konfigurera",
|
||||||
"history_auto": "Automatiskt",
|
"history_auto": "Automatiskt",
|
||||||
"battery_fleet_title": "Batteriflotta",
|
"battery_fleet_title": "Batteriflotta",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Uteslut en enhet här redan innan den rapporterar låg nivå — en dammsugare som laddar sig själv, eller en telefon som varnar på egen hand.",
|
"battery_fleet_all_hint": "Uteslut en enhet här redan innan den rapporterar låg nivå — en dammsugare som laddar sig själv, eller en telefon som varnar på egen hand.",
|
||||||
"battery_fleet_add": "Lägg till ett batteri",
|
"battery_fleet_add": "Lägg till ett batteri",
|
||||||
"battery_fleet_add_hint": "Välj en batterisensor som den automatiska identifieringen missade — den läggs till i listan direkt.",
|
"battery_fleet_add_hint": "Välj en batterisensor som den automatiska identifieringen missade — den läggs till i listan direkt.",
|
||||||
|
"battery_fleet_track_self": "Spåra självladdande batterier",
|
||||||
|
"battery_fleet_track_self_hint": "Telefoner, dammsugare och andra självladdande enheter visas som laddningsbara — vid låg nivå begärs laddning, aldrig nya batterier.",
|
||||||
"battery_fleet_status_low": "Låg",
|
"battery_fleet_status_low": "Låg",
|
||||||
"battery_fleet_status_soon": "Snart",
|
"battery_fleet_status_soon": "Snart",
|
||||||
"battery_fleet_status_ok": "I ordning",
|
"battery_fleet_status_ok": "I ordning",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "Yaklaşan",
|
"due_soon": "Yaklaşan",
|
||||||
"triggered": "Tetiklendi",
|
"triggered": "Tetiklendi",
|
||||||
"trigger_replaced": "Tetikleyici değiştirildi",
|
"trigger_replaced": "Tetikleyici değiştirildi",
|
||||||
|
"trigger_removed": "Tetikleyici kaldırıldı",
|
||||||
"ok": "Tamam",
|
"ok": "Tamam",
|
||||||
"all": "Tümü",
|
"all": "Tümü",
|
||||||
"new_object": "+ Yeni Nesne",
|
"new_object": "+ Yeni Nesne",
|
||||||
@@ -524,6 +525,7 @@
|
|||||||
"group_by_label": "Grupla",
|
"group_by_label": "Grupla",
|
||||||
"state_value_help": "HA durum değerini kullanın (genellikle küçük harf, örn. \"on\"/\"off\"). Büyük/küçük harf kaydederken normalleştirilir.",
|
"state_value_help": "HA durum değerini kullanın (genellikle küçük harf, örn. \"on\"/\"off\"). Büyük/küçük harf kaydederken normalleştirilir.",
|
||||||
"target_changes_help": "Tetikleyici çalışmadan önceki eşleşen geçiş sayısı (varsayılan: 1).",
|
"target_changes_help": "Tetikleyici çalışmadan önceki eşleşen geçiş sayısı (varsayılan: 1).",
|
||||||
|
"for_minutes_state_help": "0 her değişikliği hemen sayar. Dakika ayarlanırsa yeni durumun önce o kadar sürmesi gerekir — kısa titremeler o zaman ne tetikler ne de sayılır.",
|
||||||
"qr_print_title": "QR kodlarını yazdır",
|
"qr_print_title": "QR kodlarını yazdır",
|
||||||
"qr_print_desc": "Kesip ekipmanınıza yapıştırmak için yazdırılabilir bir QR kodu sayfası oluşturun.",
|
"qr_print_desc": "Kesip ekipmanınıza yapıştırmak için yazdırılabilir bir QR kodu sayfası oluşturun.",
|
||||||
"qr_print_load": "Nesneleri yükle",
|
"qr_print_load": "Nesneleri yükle",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "Zamanında tamamlama varsayılarak.",
|
"schedule_preview_ontime": "Zamanında tamamlama varsayılarak.",
|
||||||
"schedule_preview_ends": "(seri sona erer)",
|
"schedule_preview_ends": "(seri sona erer)",
|
||||||
"adopt_problem_responsible": "Devralınan tüm görevler için sorumlu kullanıcı (isteğe bağlı)",
|
"adopt_problem_responsible": "Devralınan tüm görevler için sorumlu kullanıcı (isteğe bağlı)",
|
||||||
|
"adopt_for_minutes_hint": "Yalnızca sorun bu süre boyunca devam ederse tetikle — 0 ilk titremeye tepki verir.",
|
||||||
"adopt_problem_configure": "Yapılandır",
|
"adopt_problem_configure": "Yapılandır",
|
||||||
"history_auto": "Otomatik",
|
"history_auto": "Otomatik",
|
||||||
"battery_fleet_title": "Pil filosu",
|
"battery_fleet_title": "Pil filosu",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Bir cihazı, daha düşük şarj bildirmeden burada hariç tutun — kendi kendine şarj olan bir süpürge ya da zaten kendisi uyaran bir telefon gibi.",
|
"battery_fleet_all_hint": "Bir cihazı, daha düşük şarj bildirmeden burada hariç tutun — kendi kendine şarj olan bir süpürge ya da zaten kendisi uyaran bir telefon gibi.",
|
||||||
"battery_fleet_add": "Pil ekle",
|
"battery_fleet_add": "Pil ekle",
|
||||||
"battery_fleet_add_hint": "Otomatik keşfin atladığı bir pil sensörü seçin — hemen listeye katılır.",
|
"battery_fleet_add_hint": "Otomatik keşfin atladığı bir pil sensörü seçin — hemen listeye katılır.",
|
||||||
|
"battery_fleet_track_self": "Kendi kendine şarj olan pilleri izle",
|
||||||
|
"battery_fleet_track_self_hint": "Telefonlar, süpürgeler ve kendi kendine şarj olan diğer cihazlar şarj edilebilir olarak görünür — seviye düşükken şarj istenir, asla yeni pil istenmez.",
|
||||||
"battery_fleet_status_low": "Düşük",
|
"battery_fleet_status_low": "Düşük",
|
||||||
"battery_fleet_status_soon": "Yakında",
|
"battery_fleet_status_soon": "Yakında",
|
||||||
"battery_fleet_status_ok": "İyi durumda",
|
"battery_fleet_status_ok": "İyi durumda",
|
||||||
|
|||||||
@@ -517,6 +517,7 @@
|
|||||||
"group_by_label": "Групувати за",
|
"group_by_label": "Групувати за",
|
||||||
"state_value_help": "Використовуйте значення стану HA (зазвичай у нижньому регістрі, напр. \"on\"/\"off\"). Регістр нормалізується при збереженні.",
|
"state_value_help": "Використовуйте значення стану HA (зазвичай у нижньому регістрі, напр. \"on\"/\"off\"). Регістр нормалізується при збереженні.",
|
||||||
"target_changes_help": "Кількість відповідних переходів, після яких тригер спрацює (за замовчуванням: 1).",
|
"target_changes_help": "Кількість відповідних переходів, після яких тригер спрацює (за замовчуванням: 1).",
|
||||||
|
"for_minutes_state_help": "0 — кожна зміна зараховується одразу. З хвилинами новий стан спершу має утриматися — короткі збої тоді не спрацьовують і не рахуються.",
|
||||||
"qr_print_title": "Друкувати QR-коди",
|
"qr_print_title": "Друкувати QR-коди",
|
||||||
"qr_print_desc": "Створи сторінку для друку з QR-кодами, які можна вирізати та наклеїти на обладнання.",
|
"qr_print_desc": "Створи сторінку для друку з QR-кодами, які можна вирізати та наклеїти на обладнання.",
|
||||||
"qr_print_load": "Завантажити об'єкти",
|
"qr_print_load": "Завантажити об'єкти",
|
||||||
@@ -584,6 +585,7 @@
|
|||||||
"quick_complete_defaults_feedback_not_needed": "Не було необхідно",
|
"quick_complete_defaults_feedback_not_needed": "Не було необхідно",
|
||||||
"quick_complete_success": "Швидко позначено виконаним",
|
"quick_complete_success": "Швидко позначено виконаним",
|
||||||
"trigger_replaced": "Тригер замінено",
|
"trigger_replaced": "Тригер замінено",
|
||||||
|
"trigger_removed": "Тригер видалено",
|
||||||
"add": "Додати",
|
"add": "Додати",
|
||||||
"show_stats": "Показати статистику + графіки",
|
"show_stats": "Показати статистику + графіки",
|
||||||
"hide_stats": "Сховати статистику",
|
"hide_stats": "Сховати статистику",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "За умови вчасного виконання.",
|
"schedule_preview_ontime": "За умови вчасного виконання.",
|
||||||
"schedule_preview_ends": "(серія завершується)",
|
"schedule_preview_ends": "(серія завершується)",
|
||||||
"adopt_problem_responsible": "Відповідальний користувач для всіх прийнятих завдань (необов'язково)",
|
"adopt_problem_responsible": "Відповідальний користувач для всіх прийнятих завдань (необов'язково)",
|
||||||
|
"adopt_for_minutes_hint": "Спрацьовувати лише якщо проблема триває цей час — 0 реагує на перший збій.",
|
||||||
"adopt_problem_configure": "Налаштувати",
|
"adopt_problem_configure": "Налаштувати",
|
||||||
"history_auto": "Автоматично",
|
"history_auto": "Автоматично",
|
||||||
"battery_fleet_title": "Парк батарей",
|
"battery_fleet_title": "Парк батарей",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "Вилучіть пристрій тут ще до того, як він повідомить про низький заряд — робот-пилосос, що заряджається сам, або телефон, який попереджає сам.",
|
"battery_fleet_all_hint": "Вилучіть пристрій тут ще до того, як він повідомить про низький заряд — робот-пилосос, що заряджається сам, або телефон, який попереджає сам.",
|
||||||
"battery_fleet_add": "Додати батарею",
|
"battery_fleet_add": "Додати батарею",
|
||||||
"battery_fleet_add_hint": "Виберіть датчик батареї, який автоматичне виявлення пропустило — він одразу з'явиться у списку.",
|
"battery_fleet_add_hint": "Виберіть датчик батареї, який автоматичне виявлення пропустило — він одразу з'явиться у списку.",
|
||||||
|
"battery_fleet_track_self": "Відстежувати самозарядні батареї",
|
||||||
|
"battery_fleet_track_self_hint": "Телефони, пилососи та інші самозарядні пристрої відображаються як акумулятори — за низького заряду потрібна зарядка, а не нові батарейки.",
|
||||||
"battery_fleet_status_low": "Низький",
|
"battery_fleet_status_low": "Низький",
|
||||||
"battery_fleet_status_soon": "Незабаром",
|
"battery_fleet_status_soon": "Незабаром",
|
||||||
"battery_fleet_status_ok": "У нормі",
|
"battery_fleet_status_ok": "У нормі",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"due_soon": "即将到期",
|
"due_soon": "即将到期",
|
||||||
"triggered": "已触发",
|
"triggered": "已触发",
|
||||||
"trigger_replaced": "触发器已更换",
|
"trigger_replaced": "触发器已更换",
|
||||||
|
"trigger_removed": "触发器已移除",
|
||||||
"ok": "正常",
|
"ok": "正常",
|
||||||
"all": "全部",
|
"all": "全部",
|
||||||
"new_object": "+ 新建维护项",
|
"new_object": "+ 新建维护项",
|
||||||
@@ -518,6 +519,7 @@
|
|||||||
"group_by_label": "分组依据",
|
"group_by_label": "分组依据",
|
||||||
"state_value_help": "使用 HA 状态值(通常为小写,例如 \"on\"/\"off\")。保存时大小写会自动规范化。",
|
"state_value_help": "使用 HA 状态值(通常为小写,例如 \"on\"/\"off\")。保存时大小写会自动规范化。",
|
||||||
"target_changes_help": "触发前需要满足的状态转换次数(默认为 1)。",
|
"target_changes_help": "触发前需要满足的状态转换次数(默认为 1)。",
|
||||||
|
"for_minutes_state_help": "0 表示每次变化立即计数。设置分钟后,新状态需先保持该时长——短暂闪变既不触发也不计数。",
|
||||||
"qr_print_title": "打印二维码",
|
"qr_print_title": "打印二维码",
|
||||||
"qr_print_desc": "生成一张包含多个二维码的可打印页面,方便裁剪并贴在设备上。",
|
"qr_print_desc": "生成一张包含多个二维码的可打印页面,方便裁剪并贴在设备上。",
|
||||||
"qr_print_load": "加载设备",
|
"qr_print_load": "加载设备",
|
||||||
@@ -819,6 +821,7 @@
|
|||||||
"schedule_preview_ontime": "假设按时完成。",
|
"schedule_preview_ontime": "假设按时完成。",
|
||||||
"schedule_preview_ends": "(系列结束)",
|
"schedule_preview_ends": "(系列结束)",
|
||||||
"adopt_problem_responsible": "所有采用任务的负责用户(可选)",
|
"adopt_problem_responsible": "所有采用任务的负责用户(可选)",
|
||||||
|
"adopt_for_minutes_hint": "问题持续此时长后才触发——0 表示对首次闪变立即响应。",
|
||||||
"adopt_problem_configure": "配置",
|
"adopt_problem_configure": "配置",
|
||||||
"history_auto": "自动",
|
"history_auto": "自动",
|
||||||
"battery_fleet_title": "电池群",
|
"battery_fleet_title": "电池群",
|
||||||
@@ -838,6 +841,8 @@
|
|||||||
"battery_fleet_all_hint": "在设备报告电量不足之前就可以在这里将其排除——例如会自行充电的扫地机器人,或本身就会提醒你的手机。",
|
"battery_fleet_all_hint": "在设备报告电量不足之前就可以在这里将其排除——例如会自行充电的扫地机器人,或本身就会提醒你的手机。",
|
||||||
"battery_fleet_add": "添加电池",
|
"battery_fleet_add": "添加电池",
|
||||||
"battery_fleet_add_hint": "选择自动发现遗漏的电池传感器——它会立即加入列表。",
|
"battery_fleet_add_hint": "选择自动发现遗漏的电池传感器——它会立即加入列表。",
|
||||||
|
"battery_fleet_track_self": "跟踪自充电电池",
|
||||||
|
"battery_fleet_track_self_hint": "手机、吸尘器等自充电设备显示为可充电电池——电量低时提示充电,而不是更换电池。",
|
||||||
"battery_fleet_status_low": "电量低",
|
"battery_fleet_status_low": "电量低",
|
||||||
"battery_fleet_status_soon": "即将",
|
"battery_fleet_status_soon": "即将",
|
||||||
"battery_fleet_status_ok": "正常",
|
"battery_fleet_status_ok": "正常",
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
var S="2.61.0";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})();
|
var S="2.63.1";var l="maintenance-supporter",T=`ll-strategy-dashboard-${l}`,D="hui-maintenance-supporter-strategy-editor",C=`/maintenance_supporter_strategy/maintenance-dashboard-strategy.js?v=${S}`,m=null;function v(){return m||(m=import(C)),m}async function I(){let r=await v();if(!r.MaintenanceDashboardStrategy)throw new Error("[maintenance-supporter] strategy bundle loaded but did not export MaintenanceDashboardStrategy");return r.MaintenanceDashboardStrategy}var p=class extends HTMLElement{static getCreateSuggestions(c){return{title:"Maintenance Supporter",icon:"mdi:wrench-clock"}}static async getConfigElement(){return await v(),document.createElement(D)}static async generate(c,f){return(await I()).generate(c,f)}};function M(){try{customElements.define(T,p)}catch{}}M();var w=window;w.customStrategies=w.customStrategies||[];w.customStrategies.some(r=>r.type===l&&r.strategyType==="dashboard")||w.customStrategies.push({type:l,strategyType:"dashboard",name:"Maintenance Supporter",description:"Auto-generated dashboard. Group views by area, status, floor, or due date \u2014 picked from the strategy editor or YAML.",documentationURL:"https://github.com/iluebbe/maintenance_supporter#dashboard-strategy"});(()=>{let r=window;if(r.__msStrategyHealActive)return;r.__msStrategyHealActive=!0;let c=/^\/(auth|config|developer-tools|profile|hassio|history|logbook|map|media-browser|energy|todo|calendar)\b/,f=/Timeout waiting for strategy element ll-strategy-(dashboard-)?maintenance-supporter/i,g=`custom:${l}`;function R(a){let t=[document.documentElement],n=0;for(;t.length&&n<9e3;){let o=t.pop();if(n++,!o)continue;let e=o;if(e.nodeType===1&&e.tagName&&e.tagName.toLowerCase()===a)return e;e.shadowRoot&&t.push(e.shadowRoot);let i=o.children;if(i)for(let d of Array.from(i))t.push(d)}return null}function k(a){let t=a?.views;if(!Array.isArray(t)||!t.length)return null;let n=window.location.pathname.split("/").filter(Boolean).pop()||"",o=t.find(i=>i?.path===n);if(o)return o;let e=Number(n);return Number.isInteger(e)&&t[e]?t[e]:t[0]}function b(){try{let t=R("ha-panel-lovelace")?.lovelace;if(!t)return!1;let n=o=>o?.type;for(let o of[t.config,t.rawConfig]){if(!o)continue;if(n(o.strategy)===g)return!0;let e=k(o);if(e&&n(e.strategy)===g)return!0}return!1}catch{return!1}}function A(){let a=!1,t=0,n=!1,o=!1,e=[document.documentElement],i=0;for(;e.length&&i<9e3;){let d=e.pop();if(i++,!d)continue;let u=d;if(u.nodeType===1&&u.tagName){let s=u.tagName.toLowerCase();(s==="hui-view"||s==="hui-sections-view")&&(a=!0),(s==="ha-card"||s==="hui-card")&&t++,s==="hui-empty-state-card"&&(o=!0),s==="hui-error-card"&&f.test(u.textContent||"")&&(n=!0)}u.shadowRoot&&e.push(u.shadowRoot);let E=d.children;if(E)for(let s of Array.from(E))e.push(s)}return n?!0:o?!1:a&&t<3&&b()}let N="/maintenance_supporter_strategy_shim.js",y=0,_=0;function L(){let a=Date.now();a-_<5e3||y>=3||(_=a,y+=1,import(`${N}?heal=${a}`).catch(()=>{}).finally(()=>{let t=window.location.pathname+window.location.search;history.pushState(null,"","/lovelace"),window.dispatchEvent(new CustomEvent("location-changed")),window.setTimeout(()=>{history.pushState(null,"",t),window.dispatchEvent(new CustomEvent("location-changed"))},200)}))}function h(){if(c.test(window.location.pathname))return;let a=0,t=Date.now(),n=window.setInterval(()=>{a++;try{if(Date.now()-t<6e3)return;if(c.test(window.location.pathname)){window.clearInterval(n);return}A()?L():window.clearInterval(n),a>=30&&window.clearInterval(n)}catch{window.clearInterval(n)}},500)}try{document.readyState==="loading"?window.addEventListener("DOMContentLoaded",h):h(),window.addEventListener("location-changed",()=>{c.test(window.location.pathname)||h()})}catch{}})();
|
||||||
|
|||||||
+51
-23
@@ -1,42 +1,42 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-TGQTH4TY.js";import{a as d}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-JPOJFRHB.js";import{a,b as _,c as t,f as l,g as h,i as g,j as n,n as i,o as v,q as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._localeReady=!1;this._userService=null;this._toggle=s=>{let o=new Set(this._selected);o.has(s)?o.delete(s):o.add(s),this._selected=o};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0})),o=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:o})),this._open=!1}catch(s){this._error=d(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return v(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,u(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="";try{this._userService?this._userService.updateHass(this.hass):this._userService=new m(this.hass);let[s,o]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=o}catch(s){this._error=d(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return t``;let s=this._lang,o=this._sensors.length>0&&this._selected.size===this._sensors.length;return t`
|
import{a as f}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-BBM5E3UT.js";import{a as p}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OGUFLW7I.js";import{a as o,b as _,c as i,f as l,g as h,k as g,l as n,p as t,r as u,t as v}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";var r=class extends h{constructor(){super(...arguments);this._open=!1;this._loading=!1;this._adopting=!1;this._error="";this._sensors=[];this._selected=new Set;this._users=[];this._responsible="";this._forMinutes="0";this._localeReady=!1;this._userService=null;this._toggle=s=>{let a=new Set(this._selected);a.has(s)?a.delete(s):a.add(s),this._selected=a};this._toggleAll=()=>{this._selected.size===this._sensors.length?this._selected=new Set:this._selected=new Set(this._sensors.map(s=>s.entity_id))};this._adopt=async()=>{if(!(this._selected.size===0||this._adopting)){this._adopting=!0,this._error="";try{let s=this._sensors.filter(e=>this._selected.has(e.entity_id)).map(e=>({entity_id:e.entity_id,name:e.name,entry_id:e.suggested_entry_id??void 0,object_name:e.suggested_object_name,device_id:e.device_id??void 0,part_id:e.suggested_part_id??void 0,responsible_user_id:this._responsible||void 0,for_minutes:parseInt(this._forMinutes,10)>0?parseInt(this._forMinutes,10):void 0})),a=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/adopt",selections:s});this.dispatchEvent(new CustomEvent("problem-sensors-adopted",{bubbles:!0,composed:!0,detail:a})),this._open=!1}catch(s){this._error=p(s,this._lang)}finally{this._adopting=!1}}}}get _lang(){return u(this.hass)}updated(s){s.has("hass")&&this.hass&&!this._localeReady&&(this._localeReady=!0,v(this._lang).then(()=>this.requestUpdate()))}async open(){this._open=!0,this._loading=!0,this._error="",this._sensors=[],this._selected=new Set,this._responsible="",this._forMinutes="0";try{this._userService?this._userService.updateHass(this.hass):this._userService=new f(this.hass);let[s,a]=await Promise.all([this.hass.connection.sendMessagePromise({type:"maintenance_supporter/problem_sensors/discover"}),this._userService.getUsers().catch(()=>[])]);this._sensors=s.sensors||[],this._selected=new Set(this._sensors.map(e=>e.entity_id)),this._users=a}catch(s){this._error=p(s,this._lang)}finally{this._loading=!1}}_close(){this._open=!1}render(){if(!this._open)return i``;let s=this._lang,a=this._sensors.length>0&&this._selected.size===this._sensors.length;return i`
|
||||||
<div class="overlay" @click=${this._close}>
|
<div class="overlay" @click=${this._close}>
|
||||||
<div class="card" @click=${e=>e.stopPropagation()}>
|
<div class="card" @click=${e=>e.stopPropagation()}>
|
||||||
<div class="title">${i("adopt_problem_title",s)}</div>
|
<div class="title">${t("adopt_problem_title",s)}</div>
|
||||||
<div class="hint">${i("adopt_problem_hint",s)}</div>
|
<div class="hint">${t("adopt_problem_hint",s)}</div>
|
||||||
${this._error?t`<div class="error">${this._error}</div>`:l}
|
${this._error?i`<div class="error">${this._error}</div>`:l}
|
||||||
|
|
||||||
${this._loading?t`<div class="loading">…</div>`:this._sensors.length===0?t`<div class="empty">${i("adopt_problem_none",s)}</div>`:t`
|
${this._loading?i`<div class="loading">…</div>`:this._sensors.length===0?i`<div class="empty">${t("adopt_problem_none",s)}</div>`:i`
|
||||||
<label class="select-all">
|
<label class="select-all">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
.checked=${o}
|
.checked=${a}
|
||||||
@change=${this._toggleAll}
|
@change=${this._toggleAll}
|
||||||
/>
|
/>
|
||||||
<span>${i("selected",s)}: ${this._selected.size} / ${this._sensors.length}</span>
|
<span>${t("selected",s)}: ${this._selected.size} / ${this._sensors.length}</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="list">
|
<div class="list">
|
||||||
${this._sensors.map(e=>{let f=this._selected.has(e.entity_id),p=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return t`
|
${this._sensors.map(e=>{let m=this._selected.has(e.entity_id),d=e.state==="on",c=[e.device_name,e.area_name].filter(Boolean).join(" \xB7 ");return i`
|
||||||
<label class="row">
|
<label class="row">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
.checked=${f}
|
.checked=${m}
|
||||||
@change=${()=>this._toggle(e.entity_id)}
|
@change=${()=>this._toggle(e.entity_id)}
|
||||||
/>
|
/>
|
||||||
<div class="row-main">
|
<div class="row-main">
|
||||||
<div class="row-top">
|
<div class="row-top">
|
||||||
<span class="row-name">${e.name}</span>
|
<span class="row-name">${e.name}</span>
|
||||||
<span class="chip ${p?"chip-active":"chip-ok"}">
|
<span class="chip ${d?"chip-active":"chip-ok"}">
|
||||||
${p?i("adopt_problem_active",s):i("adopt_problem_ok",s)}
|
${d?t("adopt_problem_active",s):t("adopt_problem_ok",s)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
${c?t`<div class="row-sub">${c}</div>`:l}
|
${c?i`<div class="row-sub">${c}</div>`:l}
|
||||||
<div class="row-target">
|
<div class="row-target">
|
||||||
→ ${e.suggested_object_name}${e.suggested_entry_id?l:t` <span class="new-tag">${i("adopt_problem_new_object",s)}</span>`}
|
→ ${e.suggested_object_name}${e.suggested_entry_id?l:i` <span class="new-tag">${t("adopt_problem_new_object",s)}</span>`}
|
||||||
</div>
|
</div>
|
||||||
${e.suggested_part_name?t`<div class="row-part">
|
${e.suggested_part_name?i`<div class="row-part">
|
||||||
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
|
<ha-icon icon="mdi:package-variant-closed"></ha-icon>
|
||||||
${i("adopt_problem_part",s).replace("{name}",e.suggested_part_name)}
|
${t("adopt_problem_part",s).replace("{name}",e.suggested_part_name)}
|
||||||
</div>`:l}
|
</div>`:l}
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
@@ -44,28 +44,43 @@ import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-TGQTH4TY
|
|||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${!this._loading&&this._sensors.length>0&&this._users.length>0?t`
|
${!this._loading&&this._sensors.length>0?i`
|
||||||
<label class="responsible">
|
<label class="responsible">
|
||||||
<span>${i("adopt_problem_responsible",s)}</span>
|
<span>${t("for_at_least_minutes",s)}</span>
|
||||||
|
<input
|
||||||
|
class="for-input"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1440"
|
||||||
|
.value=${this._forMinutes}
|
||||||
|
@input=${e=>this._forMinutes=e.target.value}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div class="for-hint">${t("adopt_for_minutes_hint",s)}</div>
|
||||||
|
`:l}
|
||||||
|
|
||||||
|
${!this._loading&&this._sensors.length>0&&this._users.length>0?i`
|
||||||
|
<label class="responsible">
|
||||||
|
<span>${t("adopt_problem_responsible",s)}</span>
|
||||||
<select
|
<select
|
||||||
.value=${this._responsible}
|
.value=${this._responsible}
|
||||||
@change=${e=>{this._responsible=e.target.value}}
|
@change=${e=>{this._responsible=e.target.value}}
|
||||||
>
|
>
|
||||||
<option value="" ?selected=${!this._responsible}>${i("no_user_assigned",s)}</option>
|
<option value="" ?selected=${!this._responsible}>${t("no_user_assigned",s)}</option>
|
||||||
${this._users.map(e=>t`<option value=${e.id} ?selected=${e.id===this._responsible}>${e.name}</option>`)}
|
${this._users.map(e=>i`<option value=${e.id} ?selected=${e.id===this._responsible}>${e.name}</option>`)}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
`:l}
|
`:l}
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<ha-button appearance="plain" @click=${this._close}>
|
<ha-button appearance="plain" @click=${this._close}>
|
||||||
${i("cancel",s)}
|
${t("cancel",s)}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
<ha-button
|
<ha-button
|
||||||
@click=${this._adopt}
|
@click=${this._adopt}
|
||||||
.disabled=${this._selected.size===0||this._adopting}
|
.disabled=${this._selected.size===0||this._adopting}
|
||||||
>
|
>
|
||||||
${i("adopt_problem_adopt",s)}
|
${t("adopt_problem_adopt",s)}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -203,6 +218,19 @@ import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-TGQTH4TY
|
|||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
.for-input {
|
||||||
|
width: 70px;
|
||||||
|
background: var(--card-background-color);
|
||||||
|
color: var(--primary-text-color);
|
||||||
|
border: 1px solid var(--divider-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
||||||
|
.for-hint {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
margin: -4px 0 2px;
|
||||||
|
}
|
||||||
.responsible select {
|
.responsible select {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
@@ -219,4 +247,4 @@ import{a as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-TGQTH4TY
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
`,a([g({attribute:!1})],r.prototype,"hass",2),a([n()],r.prototype,"_open",2),a([n()],r.prototype,"_loading",2),a([n()],r.prototype,"_adopting",2),a([n()],r.prototype,"_error",2),a([n()],r.prototype,"_sensors",2),a([n()],r.prototype,"_selected",2),a([n()],r.prototype,"_users",2),a([n()],r.prototype,"_responsible",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog};
|
`,o([g({attribute:!1})],r.prototype,"hass",2),o([n()],r.prototype,"_open",2),o([n()],r.prototype,"_loading",2),o([n()],r.prototype,"_adopting",2),o([n()],r.prototype,"_error",2),o([n()],r.prototype,"_sensors",2),o([n()],r.prototype,"_selected",2),o([n()],r.prototype,"_users",2),o([n()],r.prototype,"_responsible",2),o([n()],r.prototype,"_forMinutes",2);customElements.get("maintenance-adopt-problem-sensors-dialog")||customElements.define("maintenance-adopt-problem-sensors-dialog",r);export{r as MaintenanceAdoptProblemSensorsDialog};
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
|
||||||
function m(n,t,e){let o=new Blob([n],{type:e}),r=URL.createObjectURL(o),a=document.createElement("a");a.href=r,a.download=t,a.target="_blank",a.rel="noopener",a.style.display="none",document.body.appendChild(a),a.dispatchEvent(new MouseEvent("click")),document.body.removeChild(a),setTimeout(()=>URL.revokeObjectURL(r),6e4)}function i(n,t){let e=document.createElement("a");e.href=n,e.download=t,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}async function c(n,t,e=300){return(await n.connection.sendMessagePromise({type:"auth/sign_path",path:t,expires:e})).path}async function s(n,t,e=300){return c(n,`/api/maintenance_supporter/document/${t}`,e)}async function b(n,t,e=""){let o=window.open("about:blank","_blank");try{let r=await s(n,t);o&&(o.location.href=new URL(r+e,window.location.origin).href)}catch(r){throw o&&o.close(),r}}async function g(n,t,e){i(await s(n,t,30),e)}var d=[{key:"name",labelKey:"name",required:!0},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],u=d.map(n=>n.key),l=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function _(n){if(!Array.isArray(n))return[...l];let t=new Set,e=[];for(let o of n)typeof o=="string"&&u.includes(o)&&!t.has(o)&&(t.add(o),e.push(o));return e.length?(e.includes("name")||e.unshift("name"),e):[...l]}export{m as a,i as b,c,s as d,b as e,g as f,d as g,l as h,_ as i};
|
|
||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
var r=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestamp<this.CACHE_TTL_MS)return this.usersCache;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/users/list"});return this.usersCache=t.users,this.cacheTimestamp=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,user_id:t})}async getTasksByUser(s){return(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tasks/by_user",user_id:s})).tasks}getUserName(s){return!s||!this.usersCache?null:this.usersCache.find(t=>t.id===s)?.name||null}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}};export{r as a};
|
var r=class{constructor(s){this.usersCache=null;this.cacheTimestamp=0;this.CACHE_TTL_MS=6e4;this.hass=s}updateHass(s){this.hass=s}async getUsers(s=!1){let e=Date.now();if(!s&&this.usersCache&&e-this.cacheTimestamp<this.CACHE_TTL_MS)return this.usersCache;try{let t=await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/users/list"});return this.usersCache=t.users,this.cacheTimestamp=e,this.usersCache}catch(t){return console.error("Failed to fetch users:",t),this.usersCache||[]}}async assignUser(s,e,t){await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/task/assign_user",entry_id:s,task_id:e,user_id:t})}async getTasksByUser(s){return(await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/tasks/by_user",user_id:s})).tasks}getUserName(s){return!s||!this.usersCache?null:this.usersCache.find(t=>t.id===s)?.name||null}getUser(s){return!s||!this.usersCache?null:this.usersCache.find(e=>e.id===s)||null}getCurrentUserId(){return this.hass.user?.id||null}isCurrentUser(s){return s?s===this.getCurrentUserId():!1}clearCache(){this.usersCache=null,this.cacheTimestamp=0}};export{r as a};
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
import{a as _,e as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4Q4MHZO7.js";import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-JPOJFRHB.js";import{a as s,b,c as a,f as d,g as v,i as n,j as l,n as r,z as m}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(){this._open||(this._open=!0,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((e,t)=>[String(t),!!this.checklistPrefill[e]]).filter(([,e])=>e)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(e=>[_(e),{...e}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,o=t.files?.[0];if(t.value="",!!o){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",o,o.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(o))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=r("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=g(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?a`<span class="req-mark" aria-hidden="true">*</span>`:d}_partsCostSuggestion(){if(this.restockDefault!==null){let o=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(o)||o<=0?null:Math.round(this.restockUnitCost*o*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let o of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(o));c?.cost!=null&&(e+=c.cost*(o.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let o=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return a`<button
|
import{a as _,e as k}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VIX7PNOY.js";import{a as g}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OGUFLW7I.js";import{B as m,a as s,b,c as a,f as d,g as v,k as n,l,p as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";var i=class extends v{constructor(){super(...arguments);this.entryId="";this.taskId="";this.taskName="";this.lang="en";this.checklist=[];this.adaptiveEnabled=!1;this.taskType="";this.readingUnit="";this.restockDefault=null;this.restockUnitCost=null;this.currencySymbol="";this.parts=[];this.consumesParts=[];this.consumesInfo=[];this.requiredFields=[];this._open=!1;this._notes="";this._cost="";this._duration="";this._loading=!1;this._error="";this._checklistState={};this._feedback="needed";this._photoDocId="";this._photoPreview="";this._photoUploading=!1;this._readingValue="";this._restockQty="";this._completedAt="";this._usedParts={};this.checklistPrefill={}}open(){this._open||(this._open=!0,this._notes="",this._cost="",this._duration="",this._error="",this._checklistState=Object.fromEntries(this.checklist.map((e,t)=>[String(t),!!this.checklistPrefill[e]]).filter(([,e])=>e)),this._feedback="needed",this._photoDocId="",this._photoPreview="",this._photoUploading=!1,this._readingValue="",this._restockQty=this.restockDefault!==null?String(this.restockDefault):"",this._completedAt="",this._usedParts=Object.fromEntries(this.consumesParts.map(e=>[_(e),{...e}])))}_toggleCheck(e){let t=String(e);this._checklistState={...this._checklistState,[t]:!this._checklistState[t]}}_setFeedback(e){this._feedback=e}async _onPhotoInput(e){let t=e.target,o=t.files?.[0];if(t.value="",!!o){this._photoUploading=!0,this._error="";try{let c=new FormData;c.append("entry_id",this.entryId),c.append("tags","photo"),c.append("file",o,o.name);let p=await fetch("/api/maintenance_supporter/document/upload",{method:"POST",headers:{Authorization:`Bearer ${this.hass.auth?.data?.access_token??""}`},body:c});if(!p.ok){this._error=p.status===413?r("doc_too_large",this.lang):r("doc_upload_failed",this.lang);return}let u=await p.json();u.id&&(this._photoDocId=u.id,this._photoPreview=URL.createObjectURL(o))}catch{this._error=r("doc_upload_failed",this.lang)}finally{this._photoUploading=!1}}}_removePhoto(){this._photoPreview&&URL.revokeObjectURL(this._photoPreview),this._photoDocId="",this._photoPreview=""}async _complete(){this._loading=!0,this._error="";try{let e={type:"maintenance_supporter/task/complete",entry_id:this.entryId,task_id:this.taskId};if(this._notes&&(e.notes=this._notes),this._cost){let t=parseFloat(this._cost);!isNaN(t)&&t>=0&&(e.cost=t)}if(this._duration){let t=parseInt(this._duration,10);!isNaN(t)&&t>=0&&(e.duration=t)}if(this.checklist.length>0&&(e.checklist_state=this._checklistState),this.adaptiveEnabled&&(e.feedback=this._feedback),this._photoDocId&&(e.photo_doc_id=this._photoDocId),this._completedAt){if(new Date(this._completedAt).getTime()>Date.now()){this._error=r("completed_at_future_error",this.lang),this._loading=!1;return}e.completed_at=this._completedAt.length===16?`${this._completedAt}:00`:this._completedAt}if(this._readingValue!==""){let t=parseFloat(this._readingValue);isNaN(t)||(e.reading_value=t)}if(this.restockDefault!==null&&this._restockQty!==""){let t=parseFloat(this._restockQty);!isNaN(t)&&t>=1&&(e.restock_quantity=t)}this.parts.length>0&&(e.used_parts=Object.values(this._usedParts).filter(t=>Number.isFinite(t.quantity)&&t.quantity>0).map(t=>t.entry_id?{part_id:t.part_id,quantity:t.quantity,entry_id:t.entry_id}:{part_id:t.part_id,quantity:t.quantity})),await this.hass.connection.sendMessagePromise(e),this._open=!1,this.dispatchEvent(new CustomEvent("task-completed"))}catch(e){this._error=g(e,this.lang,r("save_error",this.lang))}finally{this._loading=!1}}get _missingRequired(){let e={notes:this._notes.trim()!=="",cost:this._cost.trim()!=="",duration:this._duration.trim()!=="",photo:this._photoDocId!=="",user:!!this.hass?.user};return this.requiredFields.filter(t=>!e[t])}_req(e){return this.requiredFields.includes(e)?a`<span class="req-mark" aria-hidden="true">*</span>`:d}_partsCostSuggestion(){if(this.restockDefault!==null){let o=parseFloat(this._restockQty);return this.restockUnitCost==null||!Number.isFinite(o)||o<=0?null:Math.round(this.restockUnitCost*o*100)/100}if(!this.parts.length)return null;let e=0,t=!1;for(let o of Object.values(this._usedParts)){let c=this.parts.find(p=>_({part_id:p.id,entry_id:p.entry_id})===_(o));c?.cost!=null&&(e+=c.cost*(o.quantity||1),t=!0)}return t?Math.round(e*100)/100:null}_renderCostSuggestion(e){if(this._cost.trim()!=="")return d;let t=this._partsCostSuggestion();if(t==null||t<=0)return d;let o=`${t.toFixed(2)}${this.currencySymbol?` ${this.currencySymbol}`:""}`;return a`<button
|
||||||
type="button"
|
type="button"
|
||||||
class="cost-suggestion"
|
class="cost-suggestion"
|
||||||
@click=${()=>this._cost=t.toFixed(2)}
|
@click=${()=>this._cost=t.toFixed(2)}
|
||||||
+10
-49
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
|||||||
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
|
import{b as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-IMKP65C5.js";async function l(e,n,t=300){return(await e.connection.sendMessagePromise({type:"auth/sign_path",path:n,expires:t})).path}async function r(e,n,t=300){return l(e,`/api/maintenance_supporter/document/${n}`,t)}async function y(e,n,t=""){let a=window.open("about:blank","_blank");try{let o=await r(e,n);a&&(a.location.href=new URL(o+t,window.location.origin).href)}catch(o){throw a&&a.close(),o}}async function p(e,n,t){i(await r(e,n,30),t)}function d(e){let n=URL.createObjectURL(new Blob([e],{type:"text/html"}));window.open(n,"_blank"),setTimeout(()=>URL.revokeObjectURL(n),6e4)}var c=[{key:"name",labelKey:"name",required:!0},{key:"manufacturer",labelKey:"manufacturer"},{key:"model",labelKey:"model"},{key:"serial_number",labelKey:"serial_number_label"},{key:"installation_date",labelKey:"installed"},{key:"warranty_expiry",labelKey:"warranty"},{key:"area_id",labelKey:"area"},{key:"documentation_url",labelKey:"documentation_url_label"},{key:"notes",labelKey:"object_notes_label"},{key:"task_count",labelKey:"tasks"},{key:"actions",labelKey:"actions"}],u=c.map(e=>e.key),s=["name","manufacturer","model","serial_number","installation_date","warranty_expiry","area_id","task_count","actions"];function g(e){if(!Array.isArray(e))return[...s];let n=new Set,t=[];for(let a of e)typeof a=="string"&&u.includes(a)&&!n.has(a)&&(n.add(a),t.push(a));return t.length?(t.includes("name")||t.unshift("name"),t):[...s]}export{l as a,r as b,y as c,p as d,d as e,c as f,s as g,g as h};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
|
function c(o,t,e){let l=new Blob([o],{type:e}),d=URL.createObjectURL(l),n=document.createElement("a");n.href=d,n.download=t,n.target="_blank",n.rel="noopener",n.style.display="none",document.body.appendChild(n),n.dispatchEvent(new MouseEvent("click")),document.body.removeChild(n),setTimeout(()=>URL.revokeObjectURL(d),6e4)}function r(o,t){let e=document.createElement("a");e.href=o,e.download=t,e.target="_blank",e.rel="noopener",e.style.display="none",document.body.appendChild(e),e.dispatchEvent(new MouseEvent("click")),document.body.removeChild(e)}export{c as a,r as b};
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-JPOJFRHB.js";import{a as r,b as _,c as l,f as o,g as p,i as d,j as s,n as i,o as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";var e=class extends p{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return h(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(a,n){this._entryId=a,this._name=n.name||"",this._manufacturer=n.manufacturer||"",this._model=n.model||"",this._serialNumber=n.serial_number||"",this._areaId=n.area_id||"",this._installationDate=n.installation_date||"",this._warrantyExpiry=n.warranty_expiry||"",this._documentationUrl=n.documentation_url||"",this._notes=n.notes||"",this._haDeviceId=n.ha_device_id||"",this._parentEntryId=n.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(a){this._error=u(a,this._lang,i("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(a=>a.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let a=this._lang,n=this._entryId?i("edit_object",a):i("new_object",a);return l`
|
import{a as u}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-OGUFLW7I.js";import{a as r,b as _,c as l,f as o,g as p,k as d,l as s,p as i,r as h}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";var e=class extends p{constructor(){super(...arguments);this.objects=[];this._open=!1;this._loading=!1;this._error="";this._name="";this._manufacturer="";this._model="";this._serialNumber="";this._areaId="";this._installationDate="";this._warrantyExpiry="";this._documentationUrl="";this._notes="";this._haDeviceId="";this._parentEntryId="";this._entryId=null}get _lang(){return h(this.hass)}openCreate(){this._entryId=null,this._name="",this._manufacturer="",this._model="",this._serialNumber="",this._areaId="",this._installationDate="",this._warrantyExpiry="",this._documentationUrl="",this._notes="",this._haDeviceId="",this._parentEntryId="",this._error="",this._open=!0}openEdit(a,n){this._entryId=a,this._name=n.name||"",this._manufacturer=n.manufacturer||"",this._model=n.model||"",this._serialNumber=n.serial_number||"",this._areaId=n.area_id||"",this._installationDate=n.installation_date||"",this._warrantyExpiry=n.warranty_expiry||"",this._documentationUrl=n.documentation_url||"",this._notes=n.notes||"",this._haDeviceId=n.ha_device_id||"",this._parentEntryId=n.parent_entry_id||"",this._error="",this._open=!0}async _save(){if(!this._loading&&this._name.trim()){this._loading=!0,this._error="";try{this._entryId?await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/update",entry_id:this._entryId,name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}):await this.hass.connection.sendMessagePromise({type:"maintenance_supporter/object/create",name:this._name,manufacturer:this._manufacturer||null,model:this._model||null,serial_number:this._serialNumber||null,area_id:this._areaId||null,installation_date:this._installationDate||null,warranty_expiry:this._warrantyExpiry||null,documentation_url:this._documentationUrl.trim()||null,notes:this._notes.trim()||null,ha_device_id:this._haDeviceId||null,parent_entry_id:this._parentEntryId||null}),this._open=!1,this.dispatchEvent(new CustomEvent("object-saved"))}catch(a){this._error=u(a,this._lang,i("save_error",this._lang))}finally{this._loading=!1}}}_parentChoices(){return(this.objects||[]).filter(a=>a.entry_id!==this._entryId)}_close(){this._open=!1}render(){if(!this._open)return l``;let a=this._lang,n=this._entryId?i("edit_object",a):i("new_object",a);return l`
|
||||||
<ha-dialog open @closed=${this._close}>
|
<ha-dialog open @closed=${this._close}>
|
||||||
<div class="dialog-title">${n}</div>
|
<div class="dialog-title">${n}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
+36
-28
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
import{a as t,b as a,c as i,f as l,g as p,i as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";var e=class extends p{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(n){let o=n.target.value;this.value=o,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:o}}))}render(){return i`
|
import{a as t,b as a,c as i,f as l,g as p,k as r}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";var e=class extends p{constructor(){super(...arguments);this.label="";this.value="";this.placeholder="";this.type="text";this.required=!1;this.disabled=!1}_onInput(n){let o=n.target.value;this.value=o,this.dispatchEvent(new CustomEvent("input",{bubbles:!0,composed:!0,detail:{value:o}}))}render(){return i`
|
||||||
<label class="field">
|
<label class="field">
|
||||||
${this.label?i`<span class="label">${this.label}${this.required?i`<span class="req">*</span>`:l}</span>`:l}
|
${this.label?i`<span class="label">${this.label}${this.required?i`<span class="req">*</span>`:l}</span>`:l}
|
||||||
<input
|
<input
|
||||||
+25
-25
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
import{n as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";var s={name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"};function c(r,o){let e=s[r];if(!e)return r;let t=a(e,o);return t&&t!==e?t:r}function d(r){let e=r.match(/data\['([^']+)'\]/)?.[1],t;return(t=r.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=r.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=r.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=r.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(r)?{field:e,rule:"required"}:(t=r.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(r)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(r)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function g(r,o,e){if(e=e??a("action_error",o),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,_=t.message||t.error?.message||"";if(!_)return e;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||e}}export{g as a};
|
import{p as a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";var s={name:"name",task_type:"maintenance_type",schedule_type:"schedule_type",interval_days:"interval_days",interval_anchor:"interval_anchor",warning_days:"warning_days",last_performed:"last_performed_optional",notes:"notes_optional",documentation_url:"documentation_url_optional",custom_icon:"custom_icon_optional",nfc_tag_id:"nfc_tag_id_optional",responsible_user_id:"responsible_user",entity_slug:"entity_slug",entity_id:"entity_id",area_id:"area_id_optional",manufacturer:"manufacturer_optional",model:"model_optional",serial_number:"serial_number_optional",installation_date:"installation_date_optional",warranty_expiry:"warranty_expiry_optional",checklist:"checklist_steps_optional",reason:"reason",feedback:"feedback",cost:"cost",duration:"duration",description:"description_optional",group_name:"name",group_description:"description_optional",environmental_entity:"environmental_entity_optional",environmental_attribute:"environmental_attribute_optional",trigger_above:"trigger_above",trigger_below:"trigger_below",trigger_equals:"trigger_equals",trigger_not_equals:"trigger_not_equals",trigger_for_minutes:"trigger_for_minutes"};function c(r,o){let e=s[r];if(!e)return r;let t=a(e,o);return t&&t!==e?t:r}function d(r){let e=r.match(/data\['([^']+)'\]/)?.[1],t;return(t=r.match(/length of value must be at most (\d+)/))?{field:e,rule:"too_long",param:t[1]}:(t=r.match(/length of value must be at least (\d+)/))?{field:e,rule:"too_short",param:t[1]}:(t=r.match(/value must be at most (\S+)/))?{field:e,rule:"value_too_high",param:t[1]}:(t=r.match(/value must be at least (\S+)/))?{field:e,rule:"value_too_low",param:t[1]}:/required key not provided/.test(r)?{field:e,rule:"required"}:(t=r.match(/expected (\w+)/))?{field:e,rule:"wrong_type",param:t[1]}:/value must be one of/.test(r)?{field:e,rule:"invalid_choice"}:/not a valid value/.test(r)?{field:e,rule:"invalid_value"}:{field:e,rule:"unknown"}}function g(r,o,e){if(e=e??a("action_error",o),typeof r=="string")return r;if(typeof r!="object"||r===null)return e;let t=r,_=t.message||t.error?.message||"";if(!_)return e;let i=d(_),l=i.field?c(i.field,o):"",n=u=>a(u,o).replace("{field}",l).replace("{n}",i.param??"");switch(i.rule){case"too_long":return n("err_too_long");case"too_short":return n("err_too_short");case"value_too_high":return n("err_value_too_high");case"value_too_low":return n("err_value_too_low");case"required":return n("err_required");case"wrong_type":return n("err_wrong_type").replace("{type}",i.param??"");case"invalid_choice":return n("err_invalid_choice");case"invalid_value":return n("err_invalid_value");default:return _||e}}export{g as a};
|
||||||
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
import{n as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";function u(e){return`${e.entry_id??""}\0${e.part_id}`}function l(e,r,s,c){let n=!!e.entry_id&&e.entry_id!==r,o=n?e.entry_id:r,a=s.find(p=>p.entry_id===o),t=(a?.parts||[]).find(p=>p.id===e.part_id)||null,d=n&&a?.object?.name||"",i=t?.name||_("shared_part_unknown",c);return{part:t,foreign:n,ownerName:d,label:d?`${i} (${d})`:i}}function P(e,r,s,c){let{part:n,label:o}=l(e,r,s,c),a=n&&n.stock!==null&&n.stock!==void 0?` (${n.stock}${n.unit?" "+n.unit:""})`:"",t=n?.storage_location?` \u2014 ${n.storage_location}`:"";return`${e.quantity}\xD7 ${o}${a}${t}`}function g(e,r,s,c){let o=(s.find(t=>t.entry_id===r)?.parts||[]).map(t=>({...t})),a=new Set(o.map(t=>u({part_id:t.id})));for(let t of e?.consumes_parts||[]){if(!t.entry_id||t.entry_id===r)continue;let d=u(t);if(a.has(d))continue;a.add(d);let{part:i,ownerName:p}=l(t,r,s,c);o.push({id:t.part_id,name:i?.name||_("shared_part_unknown",c),unit:i?.unit,stock:i?.stock??null,storage_location:i?.storage_location,entry_id:t.entry_id,owner_name:p})}return o}var f=["notes","cost","duration","photo","user"],m={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{u as a,P as b,g as c,f as d,m as e};
|
import{p as _}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";function u(e){return`${e.entry_id??""}\0${e.part_id}`}function l(e,r,s,c){let n=!!e.entry_id&&e.entry_id!==r,o=n?e.entry_id:r,a=s.find(p=>p.entry_id===o),t=(a?.parts||[]).find(p=>p.id===e.part_id)||null,d=n&&a?.object?.name||"",i=t?.name||_("shared_part_unknown",c);return{part:t,foreign:n,ownerName:d,label:d?`${i} (${d})`:i}}function P(e,r,s,c){let{part:n,label:o}=l(e,r,s,c),a=n&&n.stock!==null&&n.stock!==void 0?` (${n.stock}${n.unit?" "+n.unit:""})`:"",t=n?.storage_location?` \u2014 ${n.storage_location}`:"";return`${e.quantity}\xD7 ${o}${a}${t}`}function g(e,r,s,c){let o=(s.find(t=>t.entry_id===r)?.parts||[]).map(t=>({...t})),a=new Set(o.map(t=>u({part_id:t.id})));for(let t of e?.consumes_parts||[]){if(!t.entry_id||t.entry_id===r)continue;let d=u(t);if(a.has(d))continue;a.add(d);let{part:i,ownerName:p}=l(t,r,s,c);o.push({id:t.part_id,name:i?.name||_("shared_part_unknown",c),unit:i?.unit,stock:i?.stock??null,storage_location:i?.storage_location,entry_id:t.entry_id,owner_name:p})}return o}var f=["notes","cost","duration","photo","user"],m={notes:"notes_label",cost:"cost",duration:"duration",photo:"photo_label",user:"user_label"};export{u as a,P as b,g as c,f as d,m as e};
|
||||||
+17
-17
@@ -1,7 +1,7 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
import{a,b as v,c as n,f as g,g as b,i as m,j as c,n as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";function p(l){return l.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function x(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":p(l)}function $(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var r=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,o,s){this._entryId=e,this._taskId=i,this._objectName=o,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let o=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&o.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(o);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let o=i?.code,s=i?.message;this._error=o==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,o=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let h=this.lang||"en",d=p(i),u=p(o),_=!!this._completeResult,f=p(t("qr_action_view",h)),w=p(t("qr_action_complete",h));s.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
import{a as x}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-IMKP65C5.js";import{a,b as v,c as n,f as p,g as b,k as h,l as c,p as t}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";function d(l){return l.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function f(l){return!l.startsWith("data:image/svg+xml,")&&!l.startsWith("data:image/png;base64,")?"":d(l)}function q(l){return l.replace(/[/\\:*?"<>|#%]+/g,"").replace(/\s+/g,"-").toLowerCase().substring(0,100)}var o=class extends b{constructor(){super(...arguments);this.lang="en";this._open=!1;this._loading=!1;this._error="";this._viewResult=null;this._completeResult=null;this._urlMode="companion";this._entryId="";this._taskId=null;this._objectName="";this._taskName="";this._generateSeq=0}openForObject(e,i){this._entryId=e,this._taskId=null,this._objectName=i,this._taskName="",this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}openForTask(e,i,r,s){this._entryId=e,this._taskId=i,this._objectName=r,this._taskName=s,this._urlMode="companion",this._error="",this._viewResult=null,this._completeResult=null,this._open=!0,this._generate()}async _generate(){let e=++this._generateSeq;this._loading=!0,this._error="",this._viewResult=null,this._completeResult=null;try{let i={type:"maintenance_supporter/qr/generate",entry_id:this._entryId,url_mode:this._urlMode};this._taskId&&(i.task_id=this._taskId);let r=[this.hass.connection.sendMessagePromise({...i,action:"view"})];this._taskId&&r.push(this.hass.connection.sendMessagePromise({...i,action:"complete"}));let s=await Promise.all(r);if(e!==this._generateSeq)return;this._viewResult=s[0],s.length>1&&(this._completeResult=s[1])}catch(i){if(e!==this._generateSeq)return;let r=i?.code,s=i?.message;this._error=r==="no_url"||typeof s=="string"&&s.includes("No Home Assistant URL")?t("qr_error_no_url",this.lang):t("qr_error",this.lang)}finally{e===this._generateSeq&&(this._loading=!1)}}_setUrlMode(e){this._urlMode!==e&&(this._urlMode=e,this._generate())}_print(){if(!this._viewResult)return;let e=this._viewResult,i=e.label.task_name?`${e.label.object_name} \u2014 ${e.label.task_name}`:e.label.object_name,r=[e.label.manufacturer,e.label.model].filter(Boolean).join(" "),s=window.open("","_blank","width=600,height=500");if(!s)return;let g=this.lang||"en",u=d(i),m=d(r),_=!!this._completeResult,w=d(t("qr_action_view",g)),$=d(t("qr_action_complete",g));s.document.write(`<!DOCTYPE html><html><head><meta charset="utf-8">
|
||||||
<meta name="color-scheme" content="light">
|
<meta name="color-scheme" content="light">
|
||||||
<title>${d}</title>
|
<title>${u}</title>
|
||||||
<style>
|
<style>
|
||||||
/* Printable sheet \u2014 must not inherit the phone's dark theme. The QR images
|
/* Printable sheet \u2014 must not inherit the phone's dark theme. The QR images
|
||||||
carry their own white quiet zone and stay scannable either way, but the
|
carry their own white quiet zone and stay scannable either way, but the
|
||||||
@@ -17,25 +17,25 @@ import{a,b as v,c as n,f as g,g as b,i as m,j as c,n as t}from"/maintenance_supp
|
|||||||
.qr-label{font-size:13px;font-weight:500;color:#333}
|
.qr-label{font-size:13px;font-weight:500;color:#333}
|
||||||
.url{font-size:10px;color:#999;word-break:break-all;margin-top:8px;max-width:480px}
|
.url{font-size:10px;color:#999;word-break:break-all;margin-top:8px;max-width:480px}
|
||||||
</style></head><body>
|
</style></head><body>
|
||||||
<h2>${d}</h2>
|
<h2>${u}</h2>
|
||||||
${u?`<div class="sub">${u}</div>`:""}
|
${m?`<div class="sub">${m}</div>`:""}
|
||||||
<div class="qr-row">
|
<div class="qr-row">
|
||||||
<div class="qr-col">
|
<div class="qr-col">
|
||||||
<img src="${x(this._viewResult.svg_data_uri)}" alt="QR Info" />
|
<img src="${f(this._viewResult.svg_data_uri)}" alt="QR Info" />
|
||||||
<div class="qr-label">${f}</div>
|
<div class="qr-label">${w}</div>
|
||||||
</div>
|
</div>
|
||||||
${_?`<div class="qr-col">
|
${_?`<div class="qr-col">
|
||||||
<img src="${x(this._completeResult.svg_data_uri)}" alt="QR Complete" />
|
<img src="${f(this._completeResult.svg_data_uri)}" alt="QR Complete" />
|
||||||
<div class="qr-label">${w}</div>
|
<div class="qr-label">${$}</div>
|
||||||
</div>`:""}
|
</div>`:""}
|
||||||
</div>
|
</div>
|
||||||
<div class="url">${p(this._viewResult.url)}</div>
|
<div class="url">${d(this._viewResult.url)}</div>
|
||||||
<script>setTimeout(()=>window.print(),300)<\/script>
|
<script>setTimeout(()=>window.print(),300)<\/script>
|
||||||
</body></html>`),s.document.close()}_downloadSvg(e,i){let o=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),s=new Blob([o],{type:"image/svg+xml"}),h=URL.createObjectURL(s),d=document.createElement("a");d.href=h;let u=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;d.download=`qr-${$(u)}-${i}.svg`,d.click(),URL.revokeObjectURL(h)}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return n``;let e=this.lang||this.hass?.language||"en",i=this._taskName?`${t("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${t("qr_code",e)}: ${this._objectName}`,o=!!this._viewResult;return n`
|
</body></html>`),s.document.close()}_downloadSvg(e,i){let r=decodeURIComponent(e.svg_data_uri.replace("data:image/svg+xml,","")),s=this._taskName?`${this._objectName}-${this._taskName}`:this._objectName;x(r,`qr-${q(s)}-${i}.svg`,"image/svg+xml")}_close(){this._open=!1,this._viewResult=null,this._completeResult=null,this._error="",this._loading=!1}render(){if(!this._open)return n``;let e=this.lang||this.hass?.language||"en",i=this._taskName?`${t("qr_code",e)}: ${this._objectName} \u2014 ${this._taskName}`:`${t("qr_code",e)}: ${this._objectName}`,r=!!this._viewResult;return n`
|
||||||
<ha-dialog open @closed=${this._close}>
|
<ha-dialog open @closed=${this._close}>
|
||||||
<div class="dialog-title">${i}</div>
|
<div class="dialog-title">${i}</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this._loading?n`<div class="loading">${t("qr_generating",e)}</div>`:this._error?n`<div class="error">${this._error}</div>`:o?n`
|
${this._loading?n`<div class="loading">${t("qr_generating",e)}</div>`:this._error?n`<div class="error">${this._error}</div>`:r?n`
|
||||||
<div class="qr-pair">
|
<div class="qr-pair">
|
||||||
<div class="qr-item">
|
<div class="qr-item">
|
||||||
<img
|
<img
|
||||||
@@ -64,10 +64,10 @@ ${u?`<div class="sub">${u}</div>`:""}
|
|||||||
${t("qr_download",e)}
|
${t("qr_download",e)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
`:g}
|
`:p}
|
||||||
</div>
|
</div>
|
||||||
<div class="url-display">${this._viewResult.url}</div>
|
<div class="url-display">${this._viewResult.url}</div>
|
||||||
`:g}
|
`:p}
|
||||||
<div class="action-row">
|
<div class="action-row">
|
||||||
<label>${t("qr_url_mode",e)}</label>
|
<label>${t("qr_url_mode",e)}</label>
|
||||||
<div class="action-toggle">
|
<div class="action-toggle">
|
||||||
@@ -86,13 +86,13 @@ ${u?`<div class="sub">${u}</div>`:""}
|
|||||||
</ha-button>
|
</ha-button>
|
||||||
<ha-button
|
<ha-button
|
||||||
@click=${this._print}
|
@click=${this._print}
|
||||||
.disabled=${!o}
|
.disabled=${!r}
|
||||||
>
|
>
|
||||||
${t("qr_print",e)}
|
${t("qr_print",e)}
|
||||||
</ha-button>
|
</ha-button>
|
||||||
</div>
|
</div>
|
||||||
</ha-dialog>
|
</ha-dialog>
|
||||||
`}};r.styles=v`
|
`}};o.styles=v`
|
||||||
.dialog-title {
|
.dialog-title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@@ -210,4 +210,4 @@ ${u?`<div class="sub">${u}</div>`:""}
|
|||||||
color: var(--text-primary-color, #fff);
|
color: var(--text-primary-color, #fff);
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
`,a([m({attribute:!1})],r.prototype,"hass",2),a([m()],r.prototype,"lang",2),a([c()],r.prototype,"_open",2),a([c()],r.prototype,"_loading",2),a([c()],r.prototype,"_error",2),a([c()],r.prototype,"_viewResult",2),a([c()],r.prototype,"_completeResult",2),a([c()],r.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",r);export{r as a};
|
`,a([h({attribute:!1})],o.prototype,"hass",2),a([h()],o.prototype,"lang",2),a([c()],o.prototype,"_open",2),a([c()],o.prototype,"_loading",2),a([c()],o.prototype,"_error",2),a([c()],o.prototype,"_viewResult",2),a([c()],o.prototype,"_completeResult",2),a([c()],o.prototype,"_urlMode",2);customElements.get("maintenance-qr-dialog")||customElements.define("maintenance-qr-dialog",o);export{o as a};
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
|
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-FBQXAJIT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-VIX7PNOY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OGUFLW7I.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";export{a as MaintenanceCompleteDialog};
|
||||||
-2
@@ -1,2 +0,0 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
|
||||||
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-P4GKDOK3.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-4Q4MHZO7.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-JPOJFRHB.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";export{a as MaintenanceCompleteDialog};
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
|
||||||
import{g as a,h as b,i as c,j as d,k as e,l as f,m as g,n as h,o as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-LVFV6HYC.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-4LG4SJUR.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-H5KOKMZN.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EW357W3K.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-P4GKDOK3.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-4Q4MHZO7.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-TRENHXOS.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-TGQTH4TY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-JPOJFRHB.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";export{f as openCompleteDialog,a as openCreateObjectDialog,c as openCreateTaskDialog,b as openEditObjectDialog,d as openEditTaskDialog,e as openHistoryEditDialog,i as openObjectQuickActions,g as openQrDialog,h as openTaskQuickActions};
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
|
import{g as a,h as b,i as c,j as d,k as e,l as f,m as g,n as h,o as i}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-NWIQAC7B.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-JIB2VNVZ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-JY37H466.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NOHIK75N.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FBQXAJIT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-VIX7PNOY.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-VWJA3YLV.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IMKP65C5.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-BBM5E3UT.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OGUFLW7I.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";export{f as openCompleteDialog,a as openCreateObjectDialog,c as openCreateTaskDialog,b as openEditObjectDialog,d as openEditTaskDialog,e as openHistoryEditDialog,i as openObjectQuickActions,g as openQrDialog,h as openTaskQuickActions};
|
||||||
-2
@@ -1,2 +0,0 @@
|
|||||||
/*! maintenance_supporter frontend 2.61.0 */
|
|
||||||
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-4LG4SJUR.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-EW357W3K.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-JPOJFRHB.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-4JER2GPD.js";export{a as MaintenanceObjectDialog};
|
|
||||||
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
|
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-JIB2VNVZ.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-NOHIK75N.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-OGUFLW7I.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";export{a as MaintenanceObjectDialog};
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/*! maintenance_supporter frontend 2.63.1 */
|
||||||
|
import{a}from"/maintenance_supporter_panelfiles/panel-chunks/chunk-VWJA3YLV.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-IMKP65C5.js";import"/maintenance_supporter_panelfiles/panel-chunks/chunk-FS5TZIDQ.js";export{a as MaintenanceQrDialog};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user