mirror of
https://github.com/home-assistant/frontend.git
synced 2025-09-09 04:59:37 +00:00
Compare commits
23 Commits
20180903.0
...
20180911.0
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e3a137c675 | ||
![]() |
10aa99abdc | ||
![]() |
34567d451f | ||
![]() |
494e3dc62c | ||
![]() |
0997274f29 | ||
![]() |
76161329b6 | ||
![]() |
8505750958 | ||
![]() |
4077105db1 | ||
![]() |
3f31d83a55 | ||
![]() |
d729e3c567 | ||
![]() |
9af75f9a43 | ||
![]() |
d32d334a2e | ||
![]() |
94006a843c | ||
![]() |
4790590327 | ||
![]() |
7cf7763e21 | ||
![]() |
0d7979a72f | ||
![]() |
300425e698 | ||
![]() |
59010baf89 | ||
![]() |
47fcb122a2 | ||
![]() |
bbb50b1397 | ||
![]() |
ae8724d699 | ||
![]() |
2169f6979d | ||
![]() |
9cc577e9c7 |
28
Dockerfile
28
Dockerfile
@@ -1,25 +1,31 @@
|
||||
FROM node:8.2.1-alpine
|
||||
FROM node:8.9-alpine
|
||||
|
||||
# install yarn
|
||||
ENV PATH /root/.yarn/bin:$PATH
|
||||
|
||||
## Install/force base tools
|
||||
RUN apk update \
|
||||
&& apk add curl bash binutils tar git python3 \
|
||||
&& apk add make g++ curl bash binutils tar git python2 python3 \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& /bin/bash \
|
||||
&& touch ~/.bashrc \
|
||||
&& curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
&& touch ~/.bashrc
|
||||
|
||||
## Install yarn
|
||||
RUN curl -o- -L https://yarnpkg.com/install.sh | bash
|
||||
|
||||
## Setup the project
|
||||
RUN mkdir -p /frontend
|
||||
|
||||
WORKDIR /frontend
|
||||
|
||||
ENV NODE_ENV production
|
||||
COPY package.json yarn.lock ./
|
||||
|
||||
COPY package.json ./
|
||||
RUN yarn
|
||||
|
||||
COPY bower.json ./
|
||||
RUN ./node_modules/.bin/bower install --allow-root
|
||||
RUN yarn install --frozen-lockfile
|
||||
|
||||
COPY . .
|
||||
CMD [ "/bin/bash", "./script/build_frontend" ]
|
||||
|
||||
COPY script/docker_entrypoint.sh /usr/bin/docker_entrypoint.sh
|
||||
|
||||
RUN chmod +x /usr/bin/docker_entrypoint.sh
|
||||
|
||||
CMD [ "docker_entrypoint.sh" ]
|
||||
|
12
README.md
12
README.md
@@ -16,6 +16,18 @@ This is the repository for the official [Home Assistant](https://home-assistant.
|
||||
- Gallery: `cd gallery && script/develop_gallery`
|
||||
- Hass.io: [Instructions](https://developers.home-assistant.io/docs/en/hassio_hass.html)
|
||||
|
||||
## Frontend development
|
||||
|
||||
### Classic environment
|
||||
A complete guide can be found at the following [link](https://www.home-assistant.io/developers/frontend/). It describes a short guide for the build of project.
|
||||
|
||||
### Docker environment
|
||||
It is possible to compile the project and/or run commands in the development environment having only the [Docker](https://www.docker.com) pre-installed in the system. On the root of project you can do:
|
||||
* `sh ./script/docker_run.sh build` Build all the project with one command
|
||||
* `sh ./script/docker_run.sh bash` Open an interactive shell (the same environment generated by the *classic environment*) where you can run commands. This bash work on your project directory and any change on your file is automatically present within your build bash.
|
||||
|
||||
**Note**: if you have installed `npm` in addition to the `docker`, you can use the commands `npm run docker_build` and `npm run bash` to get a full build or bash as explained above
|
||||
|
||||
## License
|
||||
|
||||
Home Assistant is open-source and Apache 2 licensed. Feel free to browse the repository, learn and reuse parts in your own projects.
|
||||
|
@@ -10,7 +10,9 @@
|
||||
"build": "script/build_frontend",
|
||||
"lint": "eslint src hassio/src gallery/src test-mocha && polymer lint",
|
||||
"mocha": "node_modules/.bin/mocha --opts test-mocha/mocha.opts",
|
||||
"test": "npm run lint && npm run mocha"
|
||||
"test": "npm run lint && npm run mocha",
|
||||
"docker_build": "sh ./script/docker_run.sh build $npm_package_version",
|
||||
"bash": "sh ./script/docker_run.sh bash $npm_package_version"
|
||||
},
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
@@ -68,7 +70,7 @@
|
||||
"es6-object-assign": "^1.1.0",
|
||||
"eslint-import-resolver-webpack": "^0.10.0",
|
||||
"fecha": "^2.3.3",
|
||||
"home-assistant-js-websocket": "^3.0.0",
|
||||
"home-assistant-js-websocket": "^3.1.2",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"js-yaml": "^3.12.0",
|
||||
"leaflet": "^1.3.1",
|
||||
@@ -84,6 +86,7 @@
|
||||
"xss": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gfx/zopfli": "^1.0.8",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-loader": "^7.1.4",
|
||||
@@ -93,7 +96,7 @@
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"chai": "^4.1.2",
|
||||
"compression-webpack-plugin": "^1.1.11",
|
||||
"compression-webpack-plugin": "^2.0.0",
|
||||
"copy-webpack-plugin": "^4.5.1",
|
||||
"del": "^3.0.0",
|
||||
"eslint": "^4.19.1",
|
||||
|
14
script/docker_entrypoint.sh
Normal file
14
script/docker_entrypoint.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
# Docker entry point inspired by travis build and script/build_frontend
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
|
||||
# Build the frontend but not used the npm run build
|
||||
/bin/bash script/build_frontend
|
||||
|
||||
# TEST
|
||||
npm run test
|
||||
|
||||
#
|
||||
#xvfb-run wct
|
103
script/docker_run.sh
Executable file
103
script/docker_run.sh
Executable file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
# Basic Docker Management scripts
|
||||
# With this script you can build software, or enter an agnostic development environment and run commands interactively.
|
||||
|
||||
|
||||
|
||||
check_mandatory_tools(){
|
||||
if [ "x$(which docker)" == "x" ]; then
|
||||
echo "UNKNOWN - Missing docker binary! Are you sure it is installed and reachable?"
|
||||
exit 3
|
||||
fi
|
||||
|
||||
docker info > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "UNKNOWN - Unable to talk to the docker daemon! Maybe the docker daemon is not running"
|
||||
exit 3
|
||||
fi
|
||||
}
|
||||
|
||||
check_dev_image(){
|
||||
if [[ "$(docker images -q ${IMAGE_NAME}:$IMAGE_TAG 2> /dev/null)" == "" ]]; then
|
||||
echo "UNKNOWN - Can't find the development docker image ${IMAGE_NAME}:$IMAGE_TAG"
|
||||
while true; do
|
||||
read -p "Do you want to create it now?" yn
|
||||
case $yn in
|
||||
[Yy]* ) create_image; break;;
|
||||
[Nn]* ) exit 3;;
|
||||
* ) echo "Please answer y or n";;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Building the basic image for compiling the production frontend
|
||||
create_image(){
|
||||
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
|
||||
}
|
||||
|
||||
#
|
||||
# Execute interactive bash on basic image
|
||||
#
|
||||
run_bash_on_docker(){
|
||||
|
||||
check_dev_image
|
||||
|
||||
docker run -it \
|
||||
-v $PWD/:/frontend/ \
|
||||
-v /frontend/node_modules \
|
||||
-v /frontend/bower_components \
|
||||
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash
|
||||
}
|
||||
|
||||
#
|
||||
# Execute the basic image for compiling the production frontend
|
||||
#
|
||||
build_all(){
|
||||
|
||||
check_dev_image
|
||||
|
||||
docker run -it \
|
||||
-v $PWD/:/frontend/ \
|
||||
-v /frontend/node_modules \
|
||||
-v /frontend/bower_components \
|
||||
${IMAGE_NAME}:${IMAGE_TAG} /bin/bash script/build_frontend
|
||||
|
||||
}
|
||||
|
||||
# Init Global Variable
|
||||
IMAGE_NAME=home_assistant_fe_image
|
||||
IMAGE_TAG=${2:-latest}
|
||||
|
||||
check_mandatory_tools
|
||||
|
||||
case "$1" in
|
||||
setup_env)
|
||||
create_image
|
||||
;;
|
||||
bash)
|
||||
run_bash_on_docker
|
||||
;;
|
||||
build)
|
||||
build_all
|
||||
;;
|
||||
*)
|
||||
echo "NAME"
|
||||
echo " Docker Management."
|
||||
echo ""
|
||||
echo "SYNOPSIS"
|
||||
echo " ${0} command [version]"
|
||||
echo ""
|
||||
echo "DESCRIPTION"
|
||||
echo " With this script you can build software, or enter an agnostic development environment and run commands interactively."
|
||||
echo ""
|
||||
echo " The command are:"
|
||||
echo " setup_env Create develop images"
|
||||
echo " bash Run bash on develop enviroments"
|
||||
echo " build Run silent build"
|
||||
echo ""
|
||||
echo " The version is optional, if not inserted it assumes \"latest\". "
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
exit 0
|
2
setup.py
2
setup.py
@@ -1,7 +1,7 @@
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
setup(name='home-assistant-frontend',
|
||||
version='20180903.0',
|
||||
version='20180911.0',
|
||||
description='The Home Assistant frontend',
|
||||
url='https://github.com/home-assistant/home-assistant-polymer',
|
||||
author='The Home Assistant Authors',
|
||||
|
@@ -94,6 +94,12 @@ class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
|
||||
const response = await window.providersPromise;
|
||||
const authProviders = await response.json();
|
||||
|
||||
// Forward to main screen which will redirect to right onboarding page.
|
||||
if (response.status === 400 && authProviders.code === 'onboarding_required') {
|
||||
location.href = '/';
|
||||
return;
|
||||
}
|
||||
|
||||
if (authProviders.length === 0) {
|
||||
alert('No auth providers returned. Unable to finish login.');
|
||||
return;
|
||||
|
70
src/common/auth/external_auth.js
Normal file
70
src/common/auth/external_auth.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Auth class that connects to a native app for authentication.
|
||||
*/
|
||||
import { Auth } from 'home-assistant-js-websocket';
|
||||
|
||||
const CALLBACK_SET_TOKEN = 'externalAuthSetToken';
|
||||
const CALLBACK_REVOKE_TOKEN = 'externalAuthRevokeToken';
|
||||
|
||||
if (!window.externalApp && !window.webkit) {
|
||||
throw new Error('External auth requires either externalApp or webkit defined on Window object.');
|
||||
}
|
||||
|
||||
export default class ExternalAuth extends Auth {
|
||||
constructor(hassUrl) {
|
||||
super();
|
||||
|
||||
this.data = {
|
||||
hassUrl,
|
||||
access_token: '',
|
||||
// This will trigger connection to do a refresh right away
|
||||
expires: 0,
|
||||
};
|
||||
}
|
||||
|
||||
async refreshAccessToken() {
|
||||
const responseProm = new Promise((resolve, reject) => {
|
||||
window[CALLBACK_SET_TOKEN] = (success, data) => (success ? resolve(data) : reject(data));
|
||||
});
|
||||
|
||||
// Allow promise to set resolve on window object.
|
||||
await 0;
|
||||
|
||||
const callbackPayload = { callback: CALLBACK_SET_TOKEN };
|
||||
|
||||
if (window.externalApp) {
|
||||
window.externalApp.getExternalAuth(callbackPayload);
|
||||
} else {
|
||||
window.webkit.messageHandlers.getExternalAuth.postMessage(callbackPayload);
|
||||
}
|
||||
|
||||
// Response we expect back:
|
||||
// {
|
||||
// "access_token": "qwere",
|
||||
// "expires_in": 1800
|
||||
// }
|
||||
const tokens = await responseProm;
|
||||
|
||||
this.data.access_token = tokens.access_token;
|
||||
this.data.expires = (tokens.expires_in * 1000) + Date.now();
|
||||
}
|
||||
|
||||
async revoke() {
|
||||
const responseProm = new Promise((resolve, reject) => {
|
||||
window[CALLBACK_REVOKE_TOKEN] = (success, data) => (success ? resolve(data) : reject(data));
|
||||
});
|
||||
|
||||
// Allow promise to set resolve on window object.
|
||||
await 0;
|
||||
|
||||
const callbackPayload = { callback: CALLBACK_REVOKE_TOKEN };
|
||||
|
||||
if (window.externalApp) {
|
||||
window.externalApp.revokeExternalAuth(callbackPayload);
|
||||
} else {
|
||||
window.webkit.messageHandlers.revokeExternalAuth.postMessage(callbackPayload);
|
||||
}
|
||||
|
||||
await responseProm;
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ import EventsMixin from '../../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../../mixins/localize-mixin.js';
|
||||
|
||||
import computeStateName from '../../common/entity/compute_state_name.js';
|
||||
import computeDomain from '../../common/entity/compute_domain.js';
|
||||
import isComponentLoaded from '../../common/config/is_component_loaded.js';
|
||||
|
||||
/*
|
||||
@@ -44,7 +45,10 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<app-toolbar>
|
||||
<paper-icon-button icon="hass:arrow-left" on-click="_backTapped"></paper-icon-button>
|
||||
<div main-title="">[[_computeStateName(stateObj)]]</div>
|
||||
<paper-button on-click="_save">[[localize('ui.dialogs.more_info_settings.save')]]</paper-button>
|
||||
<paper-button
|
||||
on-click="_save"
|
||||
disabled='[[_computeInvalid(_entityId)]]'
|
||||
>[[localize('ui.dialogs.more_info_settings.save')]]</paper-button>
|
||||
</app-toolbar>
|
||||
|
||||
<div class="form">
|
||||
@@ -55,6 +59,8 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<paper-input
|
||||
value="{{_entityId}}"
|
||||
label="[[localize('ui.dialogs.more_info_settings.entity_id')]]"
|
||||
error-message="Domain needs to stay the same"
|
||||
invalid='[[_computeInvalid(_entityId)]]'
|
||||
></paper-input>
|
||||
</div>
|
||||
`;
|
||||
@@ -90,6 +96,10 @@ class MoreInfoSettings extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return isComponentLoaded(hass, 'config.entity_registry');
|
||||
}
|
||||
|
||||
_computeInvalid(entityId) {
|
||||
return computeDomain(this.stateObj.entity_id) !== computeDomain(entityId);
|
||||
}
|
||||
|
||||
_registryInfoChanged(newVal) {
|
||||
if (newVal) {
|
||||
this.setProperties({
|
||||
|
@@ -4,6 +4,7 @@ import {
|
||||
subscribeConfig,
|
||||
subscribeEntities,
|
||||
subscribeServices,
|
||||
ERR_INVALID_AUTH,
|
||||
} from 'home-assistant-js-websocket';
|
||||
|
||||
import { loadTokens, saveTokens } from '../common/auth/token_storage.js';
|
||||
@@ -11,21 +12,45 @@ import { subscribePanels } from '../data/ws-panels.js';
|
||||
import { subscribeThemes } from '../data/ws-themes.js';
|
||||
import { subscribeUser } from '../data/ws-user.js';
|
||||
|
||||
window.hassAuth = getAuth({
|
||||
hassUrl: `${location.protocol}//${location.host}`,
|
||||
saveTokens,
|
||||
loadTokens: () => Promise.resolve(loadTokens()),
|
||||
});
|
||||
const hassUrl = `${location.protocol}//${location.host}`;
|
||||
const isExternal = location.search.includes('external_auth=1');
|
||||
|
||||
window.hassConnection = window.hassAuth.then((auth) => {
|
||||
if (location.search.includes('auth_callback=1')) {
|
||||
history.replaceState(null, null, location.pathname);
|
||||
const authProm = isExternal ?
|
||||
() => import('../common/auth/external_auth.js')
|
||||
.then(mod => new mod.default(hassUrl)) :
|
||||
() => getAuth({
|
||||
hassUrl,
|
||||
saveTokens,
|
||||
loadTokens: () => Promise.resolve(loadTokens()),
|
||||
});
|
||||
|
||||
const connProm = async (auth) => {
|
||||
try {
|
||||
const conn = await createConnection({ auth });
|
||||
|
||||
// Clear url if we have been able to establish a connection
|
||||
if (location.search.includes('auth_callback=1')) {
|
||||
history.replaceState(null, null, location.pathname);
|
||||
}
|
||||
|
||||
return { auth, conn };
|
||||
} catch (err) {
|
||||
if (err !== ERR_INVALID_AUTH) {
|
||||
throw err;
|
||||
}
|
||||
// We can get invalid auth if auth tokens were stored that are no longer valid
|
||||
// Clear stored tokens.
|
||||
if (!isExternal) saveTokens(null);
|
||||
auth = await authProm();
|
||||
const conn = await createConnection({ auth });
|
||||
return { auth, conn };
|
||||
}
|
||||
return createConnection({ auth });
|
||||
});
|
||||
};
|
||||
|
||||
window.hassConnection = authProm().then(connProm);
|
||||
|
||||
// Start fetching some of the data that we will need.
|
||||
window.hassConnection.then((conn) => {
|
||||
window.hassConnection.then(({ conn }) => {
|
||||
const noop = () => {};
|
||||
subscribeEntities(conn, noop);
|
||||
subscribeConfig(conn, noop);
|
||||
|
@@ -27,9 +27,16 @@ export default superClass => class extends superClass {
|
||||
});
|
||||
}
|
||||
|
||||
_handleLogout() {
|
||||
this.hass.connection.close();
|
||||
clearState();
|
||||
document.location.href = '/';
|
||||
async _handleLogout() {
|
||||
try {
|
||||
await this.hass.auth.revoke();
|
||||
this.hass.connection.close();
|
||||
clearState();
|
||||
document.location.href = '/';
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
alert('Log out failed');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -13,6 +13,7 @@ import EventsMixin from '../../mixins/events-mixin.js';
|
||||
|
||||
import { getState } from '../../util/ha-pref-storage.js';
|
||||
import { getActiveTranslation } from '../../util/hass-translation.js';
|
||||
import { fetchWithAuth } from '../../util/fetch-with-auth.js';
|
||||
import hassCallApi from '../../util/hass-call-api.js';
|
||||
import computeStateName from '../../common/entity/compute_state_name.js';
|
||||
import { subscribePanels } from '../../data/ws-panels';
|
||||
@@ -25,7 +26,16 @@ export default superClass =>
|
||||
}
|
||||
|
||||
async _handleConnProm() {
|
||||
const [auth, conn] = await Promise.all([window.hassAuth, window.hassConnection]);
|
||||
let auth;
|
||||
let conn;
|
||||
try {
|
||||
const result = await window.hassConnection;
|
||||
auth = result.auth;
|
||||
conn = result.conn;
|
||||
} catch (err) {
|
||||
this._error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass = Object.assign({
|
||||
auth,
|
||||
@@ -80,23 +90,9 @@ export default superClass =>
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
callApi: async (method, path, parameters) => {
|
||||
const host = window.location.protocol + '//' + window.location.host;
|
||||
|
||||
try {
|
||||
if (auth.expired) await auth.refreshAccessToken();
|
||||
} catch (err) {
|
||||
if (err === ERR_INVALID_AUTH) {
|
||||
// Trigger auth flow
|
||||
location.reload();
|
||||
// ensure further JS is not executed
|
||||
await new Promise(() => {});
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
return await hassCallApi(host, auth, method, path, parameters);
|
||||
},
|
||||
callApi: async (method, path, parameters) =>
|
||||
hassCallApi(auth, method, path, parameters),
|
||||
fetchWithAuth: (path, init) => fetchWithAuth(auth, `${auth.data.hassUrl}${path}`, init),
|
||||
// For messages that do not get a response
|
||||
sendWS: (msg) => {
|
||||
// eslint-disable-next-line
|
||||
|
@@ -51,7 +51,7 @@ class HomeAssistant extends ext(PolymerElement, [
|
||||
</template>
|
||||
|
||||
<template is="dom-if" if="[[!showMain]]" restamp>
|
||||
<ha-init-page></ha-init-page>
|
||||
<ha-init-page error='[[_error]]'></ha-init-page>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
@@ -73,6 +73,10 @@ class HomeAssistant extends ext(PolymerElement, [
|
||||
computed: 'computePanelUrl(routeData)',
|
||||
observer: 'panelUrlChanged',
|
||||
},
|
||||
_error: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -1,12 +1,8 @@
|
||||
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-checkbox/paper-checkbox.js';
|
||||
import '@polymer/paper-input/paper-input.js';
|
||||
import '@polymer/paper-spinner/paper-spinner.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import { ERR_CANNOT_CONNECT, ERR_INVALID_AUTH } from 'home-assistant-js-websocket';
|
||||
|
||||
|
||||
import LocalizeMixin from '../mixins/localize-mixin.js';
|
||||
import EventsMixin from '../mixins/events-mixin.js';
|
||||
@@ -26,82 +22,26 @@ class HaInitPage extends EventsMixin(LocalizeMixin(PolymerElement)) {
|
||||
|
||||
<div class="layout vertical center center-center fit">
|
||||
<img src="/static/icons/favicon-192x192.png" height="192">
|
||||
<paper-spinner active="true"></paper-spinner>
|
||||
Loading data
|
||||
<paper-spinner active="[[!error]]"></paper-spinner>
|
||||
<template is='dom-if' if='[[error]]'>
|
||||
Unable to connect to Home Assistant.
|
||||
<paper-button on-click='_retry'>Retry</paper-button>
|
||||
</template>
|
||||
<template is='dom-if' if='[[!error]]'>
|
||||
Loading data
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
ready() {
|
||||
super.ready();
|
||||
this.addEventListener('keydown', ev => this.passwordKeyDown(ev));
|
||||
static get properties() {
|
||||
return {
|
||||
error: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
computeLoadingMsg(isValidating) {
|
||||
return isValidating ? 'Connecting' : 'Loading data';
|
||||
}
|
||||
|
||||
computeShowSpinner(forceShowLoading, isValidating) {
|
||||
return forceShowLoading || isValidating;
|
||||
}
|
||||
|
||||
isValidatingChanged(newVal) {
|
||||
if (!newVal) {
|
||||
setTimeout(() => {
|
||||
if (this.$.passwordInput.inputElement.inputElement) {
|
||||
this.$.passwordInput.inputElement.inputElement.focus();
|
||||
}
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
passwordKeyDown(ev) {
|
||||
// validate on enter
|
||||
if (ev.keyCode === 13) {
|
||||
this.validatePassword();
|
||||
ev.preventDefault();
|
||||
// clear error after we start typing again
|
||||
} else if (this.errorMessage) {
|
||||
this.errorMessage = '';
|
||||
}
|
||||
}
|
||||
|
||||
validatePassword() {
|
||||
var auth = this.password;
|
||||
this.$.hideKeyboardOnFocus.focus();
|
||||
const connProm = window.createHassConnection(auth);
|
||||
this.fire('try-connection', { connProm });
|
||||
|
||||
if (this.$.rememberLogin.checked) {
|
||||
connProm.then(function () {
|
||||
localStorage.authToken = auth;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
handleConnectionPromiseChanged(newVal) {
|
||||
if (!newVal) return;
|
||||
|
||||
var el = this;
|
||||
this.isValidating = true;
|
||||
|
||||
this.connectionPromise.then(
|
||||
function () {
|
||||
el.isValidating = false;
|
||||
el.password = '';
|
||||
},
|
||||
function (errCode) {
|
||||
el.isValidating = false;
|
||||
|
||||
if (errCode === ERR_CANNOT_CONNECT) {
|
||||
el.errorMessage = 'Unable to connect';
|
||||
} else if (errCode === ERR_INVALID_AUTH) {
|
||||
el.errorMessage = 'Invalid password';
|
||||
} else {
|
||||
el.errorMessage = 'Unknown error: ' + errCode;
|
||||
}
|
||||
}
|
||||
);
|
||||
_retry() {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -4,11 +4,8 @@ import '@polymer/paper-input/paper-input.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import hassCallApi from '../util/hass-call-api.js';
|
||||
import localizeLiteMixin from '../mixins/localize-lite-mixin.js';
|
||||
|
||||
const callApi = (method, path, data) => hassCallApi('', {}, method, path, data);
|
||||
|
||||
class HaOnboarding extends localizeLiteMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
@@ -141,12 +138,23 @@ class HaOnboarding extends localizeLiteMixin(PolymerElement) {
|
||||
this._errorMsg = '';
|
||||
|
||||
try {
|
||||
await callApi('post', 'onboarding/users', {
|
||||
name: this._name,
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
const response = await fetch('/api/onboarding/users', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
name: this._name,
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// eslint-disable-next-line
|
||||
throw {
|
||||
message: `Bad response from server: ${response.status}`
|
||||
};
|
||||
}
|
||||
|
||||
document.location = '/';
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
|
@@ -97,9 +97,7 @@ class HaConfigCloudAccount extends EventsMixin(PolymerElement) {
|
||||
With the Alexa integration for Home Assistant Cloud you'll be able to control all your Home Assistant devices via any Alexa-enabled device.
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://alexa.amazon.com/spa/index.html#skills/dp/B0772J1QKB/?ref=skill_dsk_skb_sr_2" target="_blank">
|
||||
Activate the Home Assistant skill for Alexa
|
||||
</a>
|
||||
To activate, search in the Alexa app for the Home Assistant Smart Home skill.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://www.home-assistant.io/cloud/alexa/" target="_blank">
|
||||
|
@@ -29,9 +29,6 @@ class HaUserEditor extends EventsMixin(NavigateMixin(LocalizeMixin(PolymerElemen
|
||||
paper-card:last-child {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
paper-button {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<hass-subpage header="View user">
|
||||
@@ -57,9 +54,12 @@ class HaUserEditor extends EventsMixin(NavigateMixin(LocalizeMixin(PolymerElemen
|
||||
</paper-card>
|
||||
<paper-card>
|
||||
<div class='card-actions'>
|
||||
<paper-button on-click='_deleteUser'>
|
||||
<paper-button on-click='_deleteUser' disabled='[[user.system_generated]]'>
|
||||
[[localize('ui.panel.config.users.editor.delete_user')]]
|
||||
</paper-button>
|
||||
<template is='dom-if' if='[[user.system_generated]]'>
|
||||
Unable to remove system generated users.
|
||||
</template>
|
||||
</div>
|
||||
</paper-card>
|
||||
</hass-subpage>
|
||||
|
@@ -50,7 +50,12 @@ class HaUserPicker extends EventsMixin(NavigateMixin(LocalizeMixin(PolymerElemen
|
||||
<paper-item>
|
||||
<paper-item-body two-line>
|
||||
<div>[[_withDefault(user.name, 'Unnamed User')]]</div>
|
||||
<div secondary="">[[user.id]]</div>
|
||||
<div secondary="">
|
||||
[[user.id]]
|
||||
<template is='dom-if' if='[[user.system_generated]]'>
|
||||
- System Generated
|
||||
</template>
|
||||
</div>
|
||||
</paper-item-body>
|
||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||
</paper-item>
|
||||
|
@@ -21,7 +21,6 @@ import './zwave-groups.js';
|
||||
import './zwave-log.js';
|
||||
import './zwave-network.js';
|
||||
import './zwave-node-config.js';
|
||||
import './zwave-node-information.js';
|
||||
import './zwave-usercodes.js';
|
||||
import './zwave-values.js';
|
||||
import './zwave-node-protection.js';
|
||||
@@ -29,12 +28,14 @@ import './zwave-node-protection.js';
|
||||
import sortByName from '../../../common/entity/states_sort_by_name.js';
|
||||
import computeStateName from '../../../common/entity/compute_state_name.js';
|
||||
import computeStateDomain from '../../../common/entity/compute_state_domain.js';
|
||||
import EventsMixin from '../../../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../../../mixins/localize-mixin.js';
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
* @appliesMixin EventsMixin
|
||||
*/
|
||||
class HaConfigZwave extends LocalizeMixin(PolymerElement) {
|
||||
class HaConfigZwave extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style ha-form-style">
|
||||
@@ -203,13 +204,14 @@ class HaConfigZwave extends LocalizeMixin(PolymerElement) {
|
||||
service="test_node"
|
||||
hidden$="[[!showHelp]]">
|
||||
</ha-service-description>
|
||||
<paper-button on-click="_nodeMoreInfo">Node Information</paper-button>
|
||||
</div>
|
||||
|
||||
<div class="device-picker">
|
||||
<paper-dropdown-menu label="Entities of this node" dynamic-align="" class="flex">
|
||||
<paper-listbox slot="dropdown-content" selected="{{selectedEntity}}">
|
||||
<template is="dom-repeat" items="[[entities]]" as="state">
|
||||
<paper-item>[[computeSelectCaptionEnt(state)]]</paper-item>
|
||||
<paper-item>[[state.entity_id]]</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
@@ -269,12 +271,6 @@ class HaConfigZwave extends LocalizeMixin(PolymerElement) {
|
||||
</paper-card>
|
||||
|
||||
<template is="dom-if" if="[[computeIsNodeSelected(selectedNode)]]">
|
||||
<!--Node info card-->
|
||||
<zwave-node-information
|
||||
id="zwave-node-information"
|
||||
nodes="[[nodes]]"
|
||||
selected-node="[[selectedNode]]"
|
||||
></zwave-node-information>
|
||||
|
||||
<!--Value card-->
|
||||
<zwave-values
|
||||
@@ -563,6 +559,10 @@ class HaConfigZwave extends LocalizeMixin(PolymerElement) {
|
||||
};
|
||||
}
|
||||
|
||||
_nodeMoreInfo() {
|
||||
this.fire('hass-more-info', { entityId: this.nodes[this.selectedNode].entity_id });
|
||||
}
|
||||
|
||||
_saveEntity() {
|
||||
const data = {
|
||||
ignored: this.entityIgnored,
|
||||
|
@@ -26,20 +26,18 @@ class OzwLog extends PolymerElement {
|
||||
padding-right: 24px;
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<ha-config-section is-wide="[[isWide]]">
|
||||
<span slot="header">OZW Log</span>
|
||||
<paper-card>
|
||||
<div class="device-picker">
|
||||
<paper-input label="Number of last log lines." type="number" min="0" max="1000" step="10" value="{{numLogLines}}">
|
||||
</paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button raised="" on-click="refreshLog">Refresh</paper-button>
|
||||
</div>
|
||||
<div class="help-text">
|
||||
<pre>[[ozwLogs]]</pre>
|
||||
</div>
|
||||
<div class="device-picker">
|
||||
<paper-input label="Number of last log lines." type="number" min="0" max="1000" step="10" value="{{_numLogLines}}">
|
||||
</paper-input>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<paper-button raised="true" on-click="_openLogWindow">Load</paper-button>
|
||||
<paper-button raised="true" on-click="_tailLog" disabled="{{_completeLog}}">Tail</paper-button>
|
||||
</paper-card>
|
||||
</ha-config-section>
|
||||
`;
|
||||
@@ -54,25 +52,53 @@ class OzwLog extends PolymerElement {
|
||||
value: false,
|
||||
},
|
||||
|
||||
ozwLogs: {
|
||||
type: String,
|
||||
value: 'Refresh to pull log'
|
||||
_ozwLogs: String,
|
||||
|
||||
_completeLog: {
|
||||
type: Boolean,
|
||||
value: true
|
||||
},
|
||||
|
||||
numLogLines: {
|
||||
_numLogLines: {
|
||||
type: Number,
|
||||
value: 0
|
||||
value: 0,
|
||||
observer: '_isCompleteLog'
|
||||
},
|
||||
|
||||
_intervalId: String,
|
||||
};
|
||||
}
|
||||
|
||||
refreshLog() {
|
||||
this.ozwLogs = 'Loading ozw log...';
|
||||
this.hass.callApi('GET', 'zwave/ozwlog?lines=' + this.numLogLines)
|
||||
.then((info) => {
|
||||
this.ozwLogs = info;
|
||||
});
|
||||
async _tailLog() {
|
||||
const ozwWindow = await this._openLogWindow();
|
||||
this.setProperties({
|
||||
_intervalId: setInterval(() => { this._refreshLog(ozwWindow); }, 1500) });
|
||||
}
|
||||
|
||||
async _openLogWindow() {
|
||||
const info = await this.hass.callApi('GET', 'zwave/ozwlog?lines=' + this._numLogLines);
|
||||
this.setProperties({ _ozwLogs: info });
|
||||
const ozwWindow = window.open('', 'OpenZwave internal log', 'toolbar');
|
||||
ozwWindow.document.title = 'OpenZwave internal logfile';
|
||||
ozwWindow.document.body.innerText = this._ozwLogs;
|
||||
return ozwWindow;
|
||||
}
|
||||
|
||||
async _refreshLog(ozwWindow) {
|
||||
if (ozwWindow.closed === true) {
|
||||
clearInterval(this._intervalId);
|
||||
this.setProperties({ _intervalId: null });
|
||||
} else {
|
||||
const info = await this.hass.callApi('GET', 'zwave/ozwlog?lines=' + this._numLogLines);
|
||||
this.setProperties({ _ozwLogs: info });
|
||||
ozwWindow.document.body.innerText = this._ozwLogs;
|
||||
}
|
||||
}
|
||||
|
||||
_isCompleteLog() {
|
||||
if (this._numLogLines !== '0') {
|
||||
this.setProperties({ _completeLog: false });
|
||||
} else { this.setProperties({ _completeLog: true }); }
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ozw-log', OzwLog);
|
||||
|
@@ -1,74 +0,0 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
class ZwaveNodeInformation extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
.content {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.node-info {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
paper-card {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
paper-button[toggles][active] {
|
||||
background: lightgray;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="content">
|
||||
<paper-card heading="Node Information">
|
||||
<div class="card-actions">
|
||||
<paper-button toggles="" raised="" noink="" active="{{nodeInfoActive}}">Show</paper-button>
|
||||
</div>
|
||||
<template is="dom-if" if="{{nodeInfoActive}}">
|
||||
<template is="dom-repeat" items="[[selectedNodeAttrs]]" as="state">
|
||||
<div class="node-info">
|
||||
<span>[[state]]</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</paper-card>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
nodes: Array,
|
||||
|
||||
selectedNode: {
|
||||
type: Number,
|
||||
value: -1,
|
||||
observer: 'nodeChanged'
|
||||
},
|
||||
|
||||
selectedNodeAttrs: Array,
|
||||
|
||||
nodeInfoActive: Boolean,
|
||||
};
|
||||
}
|
||||
|
||||
nodeChanged(selectedNode) {
|
||||
if (!this.nodes || selectedNode === -1) return;
|
||||
const nodeAttrs = this.nodes[this.selectedNode].attributes;
|
||||
const att = [];
|
||||
Object.keys(nodeAttrs).forEach((key) => {
|
||||
att.push(key + ': ' + nodeAttrs[key]);
|
||||
});
|
||||
this.selectedNodeAttrs = att.sort();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('zwave-node-information', ZwaveNodeInformation);
|
@@ -9,6 +9,24 @@ import computeStateName from '../../../common/entity/compute_state_name.js';
|
||||
|
||||
class HuiGenericEntityRow extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<template is="dom-if" if="[[_stateObj]]">
|
||||
${this.stateBadgeTemplate}
|
||||
<div class="flex">
|
||||
${this.infoTemplate}
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_stateObj]]">
|
||||
<div class="not-found">
|
||||
Entity not available: [[config.entity]]
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
@@ -50,36 +68,36 @@ class HuiGenericEntityRow extends PolymerElement {
|
||||
flex: 0 0 40px;
|
||||
}
|
||||
</style>
|
||||
<template is="dom-if" if="[[_stateObj]]">
|
||||
<state-badge
|
||||
state-obj="[[_stateObj]]"
|
||||
override-icon="[[config.icon]]"
|
||||
></state-badge>
|
||||
<div class="flex">
|
||||
<div class="info">
|
||||
[[_computeName(config.name, _stateObj)]]
|
||||
<template is="dom-if" if="[[config.secondary_info]]">
|
||||
<template is="dom-if" if="[[_equals(config.secondary_info, 'entity-id')]]">
|
||||
<div class="secondary">
|
||||
[[_stateObj.entity_id]]
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(config.secondary_info, 'last-changed')]]">
|
||||
<ha-relative-time
|
||||
hass="[[hass]]"
|
||||
datetime="[[_stateObj.last_changed]]"
|
||||
></ha-relative-time>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_stateObj]]">
|
||||
<div class="not-found">
|
||||
Entity not available: [[config.entity]]
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get stateBadgeTemplate() {
|
||||
return html`
|
||||
<state-badge
|
||||
state-obj="[[_stateObj]]"
|
||||
override-icon="[[config.icon]]"
|
||||
></state-badge>
|
||||
`;
|
||||
}
|
||||
|
||||
static get infoTemplate() {
|
||||
return html`
|
||||
<div class="info">
|
||||
[[_computeName(config.name, _stateObj)]]
|
||||
<template is="dom-if" if="[[config.secondary_info]]">
|
||||
<template is="dom-if" if="[[_equals(config.secondary_info, 'entity-id')]]">
|
||||
<div class="secondary">
|
||||
[[_stateObj.entity_id]]
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(config.secondary_info, 'last-changed')]]">
|
||||
<ha-relative-time
|
||||
hass="[[hass]]"
|
||||
datetime="[[_stateObj.last_changed]]"
|
||||
></ha-relative-time>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -6,21 +6,33 @@ import '../components/hui-generic-entity-row.js';
|
||||
|
||||
class HuiClimateEntityRow extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
${this.climateControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
ha-climate-state {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<hui-generic-entity-row
|
||||
`;
|
||||
}
|
||||
|
||||
static get climateControlTemplate() {
|
||||
return html`
|
||||
<ha-climate-state
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<ha-climate-state
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></ha-climate-state>
|
||||
</hui-generic-entity-row>
|
||||
state-obj="[[_stateObj]]"
|
||||
></ha-climate-state>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -8,6 +8,18 @@ import CoverEntity from '../../../util/cover-model.js';
|
||||
|
||||
class HuiCoverEntityRow extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
${this.coverControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
ha-cover-controls,
|
||||
@@ -15,17 +27,17 @@ class HuiCoverEntityRow extends PolymerElement {
|
||||
margin-right: -.57em;
|
||||
}
|
||||
</style>
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<template is="dom-if" if="[[!_entityObj.isTiltOnly]]">
|
||||
<ha-cover-controls hass="[[hass]]" state-obj="[[_stateObj]]"></ha-cover-controls>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_entityObj.isTiltOnly]]">
|
||||
<ha-cover-tilt-controls hass="[[hass]]" state-obj="[[_stateObj]]"></ha-cover-tilt-controls>
|
||||
</template>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get coverControlTemplate() {
|
||||
return html`
|
||||
<template is="dom-if" if="[[!_entityObj.isTiltOnly]]">
|
||||
<ha-cover-controls hass="[[hass]]" state-obj="[[_stateObj]]"></ha-cover-controls>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_entityObj.isTiltOnly]]">
|
||||
<ha-cover-tilt-controls hass="[[hass]]" state-obj="[[_stateObj]]"></ha-cover-tilt-controls>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -18,21 +18,27 @@ class HuiGroupEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<template is="dom-if" if="[[_canToggle]]">
|
||||
<ha-entity-toggle
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></ha-entity-toggle>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_canToggle]]">
|
||||
<div>
|
||||
[[_computeState(_stateObj)]]
|
||||
</div>
|
||||
</template>
|
||||
${this.groupControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get groupControlTemplate() {
|
||||
return html`
|
||||
<template is="dom-if" if="[[_canToggle]]">
|
||||
<ha-entity-toggle
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></ha-entity-toggle>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_canToggle]]">
|
||||
<div>
|
||||
[[_computeState(_stateObj)]]
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
@@ -9,6 +9,19 @@ import '../components/hui-generic-entity-row.js';
|
||||
|
||||
class HuiInputNumberEntityRow extends mixinBehaviors([IronResizableBehavior], PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
id="input_number_card"
|
||||
>
|
||||
${this.inputNumberControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
.flex {
|
||||
@@ -23,41 +36,40 @@ class HuiInputNumberEntityRow extends mixinBehaviors([IronResizableBehavior], Po
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
id="input_number_card"
|
||||
>
|
||||
<div>
|
||||
<template is="dom-if" if="[[_equals(_stateObj.attributes.mode, 'slider')]]">
|
||||
<div class="flex">
|
||||
<paper-slider
|
||||
min="[[_min]]"
|
||||
max="[[_max]]"
|
||||
value="{{_value}}"
|
||||
step="[[_step]]"
|
||||
pin
|
||||
on-change="_selectedValueChanged"
|
||||
ignore-bar-touch
|
||||
></paper-slider>
|
||||
<span class="state">[[_value]] [[_stateObj.attributes.unit_of_measurement]]</span>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(_stateObj.attributes.mode, 'box')]]">
|
||||
<paper-input
|
||||
no-label-float
|
||||
auto-validate
|
||||
pattern="[0-9]+([\\.][0-9]+)?"
|
||||
step="[[_step]]"
|
||||
`;
|
||||
}
|
||||
|
||||
static get inputNumberControlTemplate() {
|
||||
return html`
|
||||
<div>
|
||||
<template is="dom-if" if="[[_equals(_stateObj.attributes.mode, 'slider')]]">
|
||||
<div class="flex">
|
||||
<paper-slider
|
||||
min="[[_min]]"
|
||||
max="[[_max]]"
|
||||
value="{{_value}}"
|
||||
type="number"
|
||||
step="[[_step]]"
|
||||
pin
|
||||
on-change="_selectedValueChanged"
|
||||
></paper-input>
|
||||
</template>
|
||||
</div>
|
||||
</hui-generic-entity-row>
|
||||
ignore-bar-touch
|
||||
></paper-slider>
|
||||
<span class="state">[[_value]] [[_stateObj.attributes.unit_of_measurement]]</span>
|
||||
</div>
|
||||
</template>
|
||||
<template is="dom-if" if="[[_equals(_stateObj.attributes.mode, 'box')]]">
|
||||
<paper-input
|
||||
no-label-float
|
||||
auto-validate
|
||||
pattern="[0-9]+([\\.][0-9]+)?"
|
||||
step="[[_step]]"
|
||||
min="[[_min]]"
|
||||
max="[[_max]]"
|
||||
value="{{_value}}"
|
||||
type="number"
|
||||
on-change="_selectedValueChanged"
|
||||
></paper-input>
|
||||
</template>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -16,21 +16,7 @@ import EventsMixin from '../../../mixins/events-mixin.js';
|
||||
class HuiInputSelectEntityRow extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
paper-dropdown-menu {
|
||||
margin-left: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
.not-found {
|
||||
flex: 1;
|
||||
background-color: yellow;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
${this.styleTemplate}
|
||||
<template is="dom-if" if="[[_stateObj]]">
|
||||
<state-badge state-obj="[[_stateObj]]"></state-badge>
|
||||
<paper-dropdown-menu on-click="_stopPropagation" selected-item-label="{{_selected}}" label="[[_computeName(_config.name, _stateObj)]]">
|
||||
@@ -49,6 +35,26 @@ class HuiInputSelectEntityRow extends EventsMixin(PolymerElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
paper-dropdown-menu {
|
||||
margin-left: 16px;
|
||||
flex: 1;
|
||||
}
|
||||
.not-found {
|
||||
flex: 1;
|
||||
background-color: yellow;
|
||||
padding: 8px;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
@@ -11,21 +11,27 @@ class HuiInputTextEntityRow extends PolymerElement {
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<paper-input
|
||||
no-label-float
|
||||
minlength="[[_stateObj.attributes.min]]"
|
||||
maxlength="[[_stateObj.attributes.max]]"
|
||||
value="{{_value}}"
|
||||
auto-validate="[[_stateObj.attributes.pattern]]"
|
||||
pattern="[[_stateObj.attributes.pattern]]"
|
||||
type="[[_stateObj.attributes.mode]]"
|
||||
on-change="_selectedValueChanged"
|
||||
placeholder="(empty value)"
|
||||
></paper-input>
|
||||
${this.inputTextControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get inputTextControlTemplate() {
|
||||
return html`
|
||||
<paper-input
|
||||
no-label-float
|
||||
minlength="[[_stateObj.attributes.min]]"
|
||||
maxlength="[[_stateObj.attributes.max]]"
|
||||
value="{{_value}}"
|
||||
auto-validate="[[_stateObj.attributes.pattern]]"
|
||||
pattern="[[_stateObj.attributes.pattern]]"
|
||||
type="[[_stateObj.attributes.mode]]"
|
||||
on-change="_selectedValueChanged"
|
||||
placeholder="(empty value)"
|
||||
></paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
@@ -11,6 +11,18 @@ import LocalizeMixin from '../../../mixins/localize-mixin.js';
|
||||
*/
|
||||
class HuiLockEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
${this.lockControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
paper-button {
|
||||
@@ -19,14 +31,14 @@ class HuiLockEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
margin-right: -.57em;
|
||||
}
|
||||
</style>
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<paper-button on-click="_callService">
|
||||
[[_computeButtonTitle(_stateObj.state)]]
|
||||
</paper-button>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get lockControlTemplate() {
|
||||
return html`
|
||||
<paper-button on-click="_callService">
|
||||
[[_computeButtonTitle(_stateObj.state)]]
|
||||
</paper-button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,18 @@ import LocalizeMixin from '../../../mixins/localize-mixin.js';
|
||||
*/
|
||||
class HuiSceneEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
${this.sceneControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
paper-button {
|
||||
@@ -19,14 +31,14 @@ class HuiSceneEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
margin-right: -.57em;
|
||||
}
|
||||
</style>
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<paper-button on-click="_callService">
|
||||
[[localize('ui.card.scene.activate')]]
|
||||
</paper-button>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get sceneControlTemplate() {
|
||||
return html`
|
||||
<paper-button on-click="_callService">
|
||||
[[localize('ui.card.scene.activate')]]
|
||||
</paper-button>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -12,6 +12,18 @@ import LocalizeMixin from '../../../mixins/localize-mixin.js';
|
||||
*/
|
||||
class HuiScriptEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
${this.scriptControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
paper-button {
|
||||
@@ -20,17 +32,17 @@ class HuiScriptEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
margin-right: -.57em;
|
||||
}
|
||||
</style>
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<template is="dom-if" if="[[_stateObj.attributes.can_cancel]]">
|
||||
<ha-entity-toggle state-obj="[[_stateObj]]" hass="[[hass]]"></ha-entity-toggle>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_stateObj.attributes.can_cancel]]">
|
||||
<paper-button on-click="_callService">[[localize('ui.card.script.execute')]]</paper-button>
|
||||
</template>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get scriptControlTemplate() {
|
||||
return html`
|
||||
<template is="dom-if" if="[[_stateObj.attributes.can_cancel]]">
|
||||
<ha-entity-toggle state-obj="[[_stateObj]]" hass="[[hass]]"></ha-entity-toggle>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_stateObj.attributes.can_cancel]]">
|
||||
<paper-button on-click="_callService">[[localize('ui.card.script.execute')]]</paper-button>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -12,20 +12,32 @@ import LocalizeMixin from '../../../mixins/localize-mixin.js';
|
||||
*/
|
||||
class HuiTextEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
${this.textControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
div {
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
<hui-generic-entity-row
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<div>
|
||||
[[_computeState(_stateObj)]]
|
||||
</div>
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get textControlTemplate() {
|
||||
return html`
|
||||
<div>
|
||||
[[_computeState(_stateObj)]]
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -13,13 +13,19 @@ class HuiTimerEntityRow extends PolymerElement {
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<div>
|
||||
[[_computeDisplay(_stateObj, _timeRemaining)]]
|
||||
</div>
|
||||
${this.timerControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get timerControlTemplate() {
|
||||
return html`
|
||||
<div>
|
||||
[[_computeDisplay(_stateObj, _timeRemaining)]]
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
@@ -18,21 +18,27 @@ class HuiToggleEntityRow extends LocalizeMixin(PolymerElement) {
|
||||
hass="[[hass]]"
|
||||
config="[[_config]]"
|
||||
>
|
||||
<template is="dom-if" if="[[_canToggle]]">
|
||||
<ha-entity-toggle
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></ha-entity-toggle>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_canToggle]]">
|
||||
<div>
|
||||
[[_computeState(_stateObj)]]
|
||||
</div>
|
||||
</template>
|
||||
${this.toggleControlTemplate}
|
||||
</hui-generic-entity-row>
|
||||
`;
|
||||
}
|
||||
|
||||
static get toggleControlTemplate() {
|
||||
return html`
|
||||
<template is="dom-if" if="[[_canToggle]]">
|
||||
<ha-entity-toggle
|
||||
hass="[[hass]]"
|
||||
state-obj="[[_stateObj]]"
|
||||
></ha-entity-toggle>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_canToggle]]">
|
||||
<div>
|
||||
[[_computeState(_stateObj)]]
|
||||
</div>
|
||||
</template>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
@@ -7,6 +7,19 @@ import callService from '../common/call-service.js';
|
||||
|
||||
class HuiCallServiceRow extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<ha-icon icon="[[_config.icon]]"></ha-icon>
|
||||
<div class="flex">
|
||||
<div>
|
||||
[[_config.name]]
|
||||
</div>
|
||||
<paper-button on-click="_callService">[[_config.action_name]]</paper-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
:host {
|
||||
@@ -36,13 +49,6 @@ class HuiCallServiceRow extends PolymerElement {
|
||||
margin-right: -.57em;
|
||||
}
|
||||
</style>
|
||||
<ha-icon icon="[[_config.icon]]"></ha-icon>
|
||||
<div class="flex">
|
||||
<div>
|
||||
[[_config.name]]
|
||||
</div>
|
||||
<paper-button on-click="_callService">[[_config.action_name]]</paper-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -5,6 +5,18 @@ import '../../../components/ha-icon.js';
|
||||
|
||||
class HuiWeblinkRow extends PolymerElement {
|
||||
static get template() {
|
||||
return html`
|
||||
${this.styleTemplate}
|
||||
<a href="[[_config.url]]">
|
||||
<ha-icon icon="[[_config.icon]]"></ha-icon>
|
||||
<div>
|
||||
[[_config.name]]
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
static get styleTemplate() {
|
||||
return html`
|
||||
<style>
|
||||
a {
|
||||
@@ -24,12 +36,6 @@ class HuiWeblinkRow extends PolymerElement {
|
||||
margin-left: 16px;
|
||||
}
|
||||
</style>
|
||||
<a href="[[_config.url]]">
|
||||
<ha-icon icon="[[_config.icon]]"></ha-icon>
|
||||
<div>
|
||||
[[_config.name]]
|
||||
</div>
|
||||
</a>
|
||||
`;
|
||||
}
|
||||
|
||||
|
158
src/panels/mailbox/ha-dialog-show-audio-message.js
Normal file
158
src/panels/mailbox/ha-dialog-show-audio-message.js
Normal file
@@ -0,0 +1,158 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-dialog/paper-dialog.js';
|
||||
import '@polymer/paper-spinner/paper-spinner.js';
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
|
||||
import '../../resources/ha-style.js';
|
||||
|
||||
import LocalizeMixin from '../../mixins/localize-mixin.js';
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaDialogShowAudioMessage extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style-dialog">
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
@media all and (max-width: 500px) {
|
||||
paper-dialog {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
max-height: calc(100% - 64px);
|
||||
|
||||
position: fixed !important;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
overflow: scroll;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
paper-dialog {
|
||||
border-radius: 2px;
|
||||
}
|
||||
paper-dialog p {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.icon {
|
||||
float: right;
|
||||
}
|
||||
</style>
|
||||
<paper-dialog id="mp3dialog" with-backdrop opened="{{_opened}}" on-opened-changed="_openedChanged">
|
||||
<h2>
|
||||
[[localize('ui.panel.mailbox.playback_title')]]
|
||||
<div class='icon'>
|
||||
<template is="dom-if" if="[[_loading]]">
|
||||
<paper-spinner active></paper-spinner>
|
||||
</template>
|
||||
<template is="dom-if" if="[[!_loading]]">
|
||||
<paper-icon-button
|
||||
on-click='openDeleteDialog'
|
||||
icon='hass:delete'
|
||||
></paper-icon-button>
|
||||
</template>
|
||||
</div>
|
||||
</h2>
|
||||
<div id="transcribe"></div>
|
||||
<div>
|
||||
<template is="dom-if" if="[[_errorMsg]]">
|
||||
<div class='error'>[[_errorMsg]]</div>
|
||||
</template>
|
||||
<audio id="mp3" preload="none" controls> <source id="mp3src" src="" type="audio/mpeg" /></audio>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
||||
_currentMessage: Object,
|
||||
|
||||
// Error message when can't talk to server etc
|
||||
_errorMsg: String,
|
||||
|
||||
_loading: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
_opened: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
showDialog({ hass, message }) {
|
||||
this.hass = hass;
|
||||
this._loading = true;
|
||||
this._errorMsg = null;
|
||||
this._currentMessage = message;
|
||||
this._opened = true;
|
||||
this.$.transcribe.innerText = message.message;
|
||||
const platform = message.platform;
|
||||
const mp3 = this.$.mp3;
|
||||
mp3.src = null;
|
||||
const url = `/api/mailbox/media/${platform}/${message.sha}`;
|
||||
this.hass.fetchWithAuth(url)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.blob();
|
||||
}
|
||||
return Promise.reject({
|
||||
status: response.status,
|
||||
statusText: response.statusText
|
||||
});
|
||||
})
|
||||
.then((blob) => {
|
||||
this._loading = false;
|
||||
mp3.src = window.URL.createObjectURL(blob);
|
||||
mp3.play();
|
||||
})
|
||||
.catch((err) => {
|
||||
this._loading = false;
|
||||
this._errorMsg = `Error loading audio: ${err.statusText}`;
|
||||
});
|
||||
}
|
||||
|
||||
openDeleteDialog() {
|
||||
if (confirm(this.localize('ui.panel.mailbox.delete_prompt'))) {
|
||||
this.deleteSelected();
|
||||
}
|
||||
}
|
||||
|
||||
deleteSelected() {
|
||||
const msg = this._currentMessage;
|
||||
this.hass.callApi('DELETE', `mailbox/delete/${msg.platform}/${msg.sha}`);
|
||||
this._dialogDone();
|
||||
}
|
||||
|
||||
_dialogDone() {
|
||||
this.$.mp3.pause();
|
||||
this.setProperties({
|
||||
_currentMessage: null,
|
||||
_errorMsg: null,
|
||||
_loading: false,
|
||||
_opened: false,
|
||||
});
|
||||
}
|
||||
|
||||
_openedChanged(ev) {
|
||||
// Closed dialog by clicking on the overlay
|
||||
// Check against dialogClosedCallback to make sure we didn't change
|
||||
// programmatically
|
||||
if (!ev.detail.value) {
|
||||
this._dialogDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('ha-dialog-show-audio-message', HaDialogShowAudioMessage);
|
@@ -3,7 +3,6 @@ import '@polymer/app-layout/app-header/app-header.js';
|
||||
import '@polymer/app-layout/app-toolbar/app-toolbar.js';
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
import '@polymer/paper-card/paper-card.js';
|
||||
import '@polymer/paper-dialog/paper-dialog.js';
|
||||
import '@polymer/paper-input/paper-textarea.js';
|
||||
import '@polymer/paper-item/paper-item-body.js';
|
||||
import '@polymer/paper-item/paper-item.js';
|
||||
@@ -17,6 +16,8 @@ import '../../resources/ha-style.js';
|
||||
import formatDateTime from '../../common/datetime/format_date_time.js';
|
||||
import LocalizeMixin from '../../mixins/localize-mixin.js';
|
||||
|
||||
let registeredDialog = false;
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
@@ -57,32 +58,8 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
paper-dialog {
|
||||
border-radius: 2px;
|
||||
}
|
||||
paper-dialog p {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
#mp3dialog paper-icon-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px) {
|
||||
paper-dialog {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
max-height: calc(100% - 64px);
|
||||
|
||||
position: fixed !important;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
overflow: scroll;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: auto;
|
||||
padding: 0;
|
||||
@@ -128,28 +105,6 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) {
|
||||
</paper-card>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
|
||||
<paper-dialog with-backdrop id="mp3dialog" on-iron-overlay-closed="_mp3Closed">
|
||||
<h2>
|
||||
[[localize('ui.panel.mailbox.playback_title')]]
|
||||
<paper-icon-button
|
||||
on-click='openDeleteDialog'
|
||||
icon='hass:delete'
|
||||
></paper-icon-button>
|
||||
</h2>
|
||||
<div id="transcribe"></div>
|
||||
<div>
|
||||
<audio id="mp3" preload="none" controls> <source id="mp3src" src="" type="audio/mpeg" /></audio>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
|
||||
<paper-dialog with-backdrop id="confirmdel">
|
||||
<p>[[localize('ui.panel.mailbox.delete_prompt')]]</p>
|
||||
<div class="buttons">
|
||||
<paper-button dialog-dismiss>[[localize('ui.common.cancel')]]</paper-button>
|
||||
<paper-button dialog-confirm autofocus on-click="deleteSelected">[[localize('ui.panel.mailbox.delete_button')]]</paper-button>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -176,15 +131,19 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) {
|
||||
_messages: {
|
||||
type: Array,
|
||||
},
|
||||
|
||||
currentMessage: {
|
||||
type: Object,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (!registeredDialog) {
|
||||
registeredDialog = true;
|
||||
this.fire('register-dialog', {
|
||||
dialogShowEvent: 'show-audio-message-dialog',
|
||||
dialogTag: 'ha-dialog-show-audio-message',
|
||||
dialogImport: () => import('./ha-dialog-show-audio-message.js'),
|
||||
});
|
||||
}
|
||||
this.hassChanged = this.hassChanged.bind(this);
|
||||
this.hass.connection.subscribeEvents(this.hassChanged, 'mailbox_updated')
|
||||
.then(function (unsub) { this._unsubEvents = unsub; }.bind(this));
|
||||
@@ -209,35 +168,19 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) {
|
||||
}
|
||||
|
||||
openMP3Dialog(event) {
|
||||
var platform = event.model.item.platform;
|
||||
this.currentMessage = event.model.item;
|
||||
this.$.mp3dialog.open();
|
||||
this.$.mp3src.src = '/api/mailbox/media/' + platform + '/' + event.model.item.sha;
|
||||
this.$.transcribe.innerText = event.model.item.message;
|
||||
this.$.mp3.load();
|
||||
this.$.mp3.play();
|
||||
this.fire('show-audio-message-dialog', {
|
||||
hass: this.hass,
|
||||
message: event.model.item,
|
||||
});
|
||||
}
|
||||
|
||||
_mp3Closed() {
|
||||
this.$.mp3.pause();
|
||||
}
|
||||
|
||||
openDeleteDialog() {
|
||||
this.$.confirmdel.open();
|
||||
}
|
||||
|
||||
deleteSelected() {
|
||||
var msg = this.currentMessage;
|
||||
this.hass.callApi('DELETE', 'mailbox/delete/' + msg.platform + '/' + msg.sha);
|
||||
this.$.mp3dialog.close();
|
||||
}
|
||||
getMessages() {
|
||||
const items = this.platforms.map(function (platform) {
|
||||
return this.hass.callApi('GET', 'mailbox/messages/' + platform).then(function (values) {
|
||||
var platformItems = [];
|
||||
var arrayLength = values.length;
|
||||
for (var i = 0; i < arrayLength; i++) {
|
||||
var datetime = formatDateTime(new Date(values[i].info.origtime * 1000));
|
||||
return this.hass.callApi('GET', `mailbox/messages/${platform}`).then(function (values) {
|
||||
const platformItems = [];
|
||||
const arrayLength = values.length;
|
||||
for (let i = 0; i < arrayLength; i++) {
|
||||
const datetime = formatDateTime(new Date(values[i].info.origtime * 1000));
|
||||
platformItems.push({
|
||||
timestamp: datetime,
|
||||
caller: values[i].info.callerid,
|
||||
@@ -251,15 +194,9 @@ class HaPanelMailbox extends LocalizeMixin(PolymerElement) {
|
||||
});
|
||||
}.bind(this));
|
||||
return Promise.all(items).then(function (platformItems) {
|
||||
var arrayLength = items.length;
|
||||
var final = [];
|
||||
for (var i = 0; i < arrayLength; i++) {
|
||||
final = final.concat(platformItems[i]);
|
||||
}
|
||||
final.sort(function (a, b) {
|
||||
return [].concat(...platformItems).sort(function (a, b) {
|
||||
return new Date(b.timestamp) - new Date(a.timestamp);
|
||||
});
|
||||
return final;
|
||||
});
|
||||
}
|
||||
|
||||
|
127
src/panels/profile/ha-long-lived-access-tokens-card.js
Normal file
127
src/panels/profile/ha-long-lived-access-tokens-card.js
Normal file
@@ -0,0 +1,127 @@
|
||||
import '@polymer/paper-button/paper-button.js';
|
||||
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import EventsMixin from '../../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../../mixins/localize-mixin.js';
|
||||
import formatDateTime from '../../common/datetime/format_date_time.js';
|
||||
|
||||
import '../../resources/ha-style.js';
|
||||
|
||||
import './ha-settings-row.js';
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaLongLivedTokens extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
.card-content {
|
||||
margin: -1em 0;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
paper-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
</style>
|
||||
<paper-card heading="[[localize('ui.panel.profile.long_lived_access_tokens.header')]]">
|
||||
<div class="card-content">
|
||||
<p>
|
||||
[[localize('ui.panel.profile.long_lived_access_tokens.description')]]
|
||||
<a href='https://developers.home-assistant.io/docs/en/auth_api.html#making-authenticated-requests' target='_blank'>
|
||||
[[localize('ui.panel.profile.long_lived_access_tokens.learn_auth_requests')]]
|
||||
</a>
|
||||
</p>
|
||||
<template is='dom-if' if='[[!_tokens.length]]'>
|
||||
<p>[[localize('ui.panel.profile.long_lived_access_tokens.empty_state')]]</p>
|
||||
</template>
|
||||
</div>
|
||||
<template is='dom-repeat' items='[[_tokens]]'>
|
||||
<ha-settings-row>
|
||||
<span slot='heading'>[[item.client_name]]</span>
|
||||
<span slot='description'>[[_formatCreatedAt(item.created_at)]]</span>
|
||||
<paper-icon-button icon="hass:delete" on-click='_handleDelete'></paper-icon-button>
|
||||
</ha-settings-row>
|
||||
</template>
|
||||
<div class='card-actions'>
|
||||
<paper-button on-click='_handleCreate'>
|
||||
[[localize('ui.panel.profile.long_lived_access_tokens.create')]]
|
||||
</paper-button>
|
||||
</div>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
refreshTokens: Array,
|
||||
_tokens: {
|
||||
type: Array,
|
||||
computed: '_computeTokens(refreshTokens)'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_computeTokens(refreshTokens) {
|
||||
return refreshTokens.filter(tkn => tkn.type === 'long_lived_access_token').reverse();
|
||||
}
|
||||
|
||||
_formatTitle(name) {
|
||||
return this.localize(
|
||||
'ui.panel.profile.long_lived_access_tokens.token_title',
|
||||
'name', name
|
||||
);
|
||||
}
|
||||
|
||||
_formatCreatedAt(created) {
|
||||
return this.localize(
|
||||
'ui.panel.profile.long_lived_access_tokens.created_at',
|
||||
'date', formatDateTime(new Date(created))
|
||||
);
|
||||
}
|
||||
|
||||
async _handleCreate() {
|
||||
const name = prompt(this.localize('ui.panel.profile.long_lived_access_tokens.prompt_name'));
|
||||
if (!name) return;
|
||||
try {
|
||||
const token = await this.hass.callWS({
|
||||
type: 'auth/long_lived_access_token',
|
||||
lifespan: 3650,
|
||||
client_name: name,
|
||||
});
|
||||
prompt(this.localize('ui.panel.profile.long_lived_access_tokens.prompt_copy_token'), token);
|
||||
this.fire('hass-refresh-tokens');
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
alert(this.localize('ui.panel.profile.long_lived_access_tokens.create_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
async _handleDelete(ev) {
|
||||
if (!confirm(this.localize('ui.panel.profile.long_lived_access_tokens.confirm_delete', 'name', ev.model.item.client_name))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: 'auth/delete_refresh_token',
|
||||
refresh_token_id: ev.model.item.id,
|
||||
});
|
||||
this.fire('hass-refresh-tokens');
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
alert(this.localize('ui.panel.profile.long_lived_access_tokens.delete_failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ha-long-lived-access-tokens-card', HaLongLivedTokens);
|
@@ -15,6 +15,9 @@ import EventsMixin from '../../mixins/events-mixin.js';
|
||||
|
||||
import './ha-change-password-card.js';
|
||||
import './ha-mfa-modules-card.js';
|
||||
import './ha-refresh-tokens-card.js';
|
||||
import './ha-long-lived-access-tokens-card.js';
|
||||
|
||||
import './ha-pick-language-row.js';
|
||||
import './ha-pick-theme-row.js';
|
||||
import './ha-push-notifications-row.js';
|
||||
@@ -84,7 +87,22 @@ class HaPanelProfile extends EventsMixin(PolymerElement) {
|
||||
<ha-change-password-card hass="[[hass]]"></ha-change-password-card>
|
||||
</template>
|
||||
|
||||
<ha-mfa-modules-card hass='[[hass]]' mfa-modules='[[hass.user.mfa_modules]]'></ha-mfa-modules-card>
|
||||
<ha-mfa-modules-card
|
||||
hass='[[hass]]'
|
||||
mfa-modules='[[hass.user.mfa_modules]]'
|
||||
></ha-mfa-modules-card>
|
||||
|
||||
<ha-refresh-tokens-card
|
||||
hass='[[hass]]'
|
||||
refresh-tokens='[[_refreshTokens]]'
|
||||
on-hass-refresh-tokens='_refreshRefreshTokens'
|
||||
></ha-refresh-tokens-card>
|
||||
|
||||
<ha-long-lived-access-tokens-card
|
||||
hass='[[hass]]'
|
||||
refresh-tokens='[[_refreshTokens]]'
|
||||
on-hass-refresh-tokens='_refreshRefreshTokens'
|
||||
></ha-long-lived-access-tokens-card>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
`;
|
||||
@@ -95,9 +113,21 @@ class HaPanelProfile extends EventsMixin(PolymerElement) {
|
||||
hass: Object,
|
||||
narrow: Boolean,
|
||||
showMenu: Boolean,
|
||||
_refreshTokens: Array,
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._refreshRefreshTokens();
|
||||
}
|
||||
|
||||
async _refreshRefreshTokens() {
|
||||
this._refreshTokens = await this.hass.callWS({
|
||||
type: 'auth/refresh_tokens'
|
||||
});
|
||||
}
|
||||
|
||||
_handleLogOut() {
|
||||
this.fire('hass-logout');
|
||||
}
|
||||
|
82
src/panels/profile/ha-refresh-tokens-card.js
Normal file
82
src/panels/profile/ha-refresh-tokens-card.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import '@polymer/paper-icon-button/paper-icon-button.js';
|
||||
|
||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||
import EventsMixin from '../../mixins/events-mixin.js';
|
||||
import LocalizeMixin from '../../mixins/localize-mixin.js';
|
||||
import formatDateTime from '../../common/datetime/format_date_time.js';
|
||||
|
||||
import './ha-settings-row.js';
|
||||
|
||||
/*
|
||||
* @appliesMixin EventsMixin
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaRefreshTokens extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style>
|
||||
paper-card {
|
||||
display: block;
|
||||
}
|
||||
paper-icon-button {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
</style>
|
||||
<paper-card heading="[[localize('ui.panel.profile.refresh_tokens.header')]]">
|
||||
<div class="card-content">[[localize('ui.panel.profile.refresh_tokens.description')]]</div>
|
||||
<template is='dom-repeat' items='[[_computeTokens(refreshTokens)]]'>
|
||||
<ha-settings-row>
|
||||
<span slot='heading'>[[_formatTitle(item.client_id)]]</span>
|
||||
<span slot='description'>[[_formatCreatedAt(item.created_at)]]</span>
|
||||
<paper-icon-button icon="hass:delete" on-click='_handleDelete'></paper-icon-button>
|
||||
</ha-settings-row>
|
||||
</template>
|
||||
</paper-card>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
refreshTokens: Array,
|
||||
};
|
||||
}
|
||||
|
||||
_computeTokens(refreshTokens) {
|
||||
return refreshTokens.filter(tkn => tkn.type === 'normal').reverse();
|
||||
}
|
||||
|
||||
_formatTitle(clientId) {
|
||||
return this.localize(
|
||||
'ui.panel.profile.refresh_tokens.token_title',
|
||||
'clientId', clientId
|
||||
);
|
||||
}
|
||||
|
||||
_formatCreatedAt(created) {
|
||||
return this.localize(
|
||||
'ui.panel.profile.refresh_tokens.created_at',
|
||||
'date', formatDateTime(new Date(created))
|
||||
);
|
||||
}
|
||||
|
||||
async _handleDelete(ev) {
|
||||
if (!confirm(this.localize('ui.panel.profile.refresh_tokens.confirm_delete', 'name', ev.model.item.client_id))) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await this.hass.callWS({
|
||||
type: 'auth/delete_refresh_token',
|
||||
refresh_token_id: ev.model.item.id,
|
||||
});
|
||||
this.fire('hass-refresh-tokens');
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line
|
||||
console.error(err);
|
||||
alert(this.localize('ui.panel.profile.refresh_tokens.delete_failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('ha-refresh-tokens-card', HaRefreshTokens);
|
@@ -746,6 +746,27 @@
|
||||
"error_no_theme": "No themes available.",
|
||||
"link_promo": "Learn about themes",
|
||||
"dropdown_label": "Theme"
|
||||
},
|
||||
"refresh_tokens": {
|
||||
"header": "Refresh Tokens",
|
||||
"description": "Each refresh token represents a login session. Refresh tokens will be automatically removed when you click log out. Below a list of refresh tokens that are currently active for your account.",
|
||||
"token_title": "Refresh token for {clientId}",
|
||||
"created_at": "Created at {date}",
|
||||
"confirm_delete": "Are you sure you want to delete the refresh token for {name}?",
|
||||
"delete_failed": "Failed to delete the refresh token."
|
||||
},
|
||||
"long_lived_access_tokens": {
|
||||
"header": "Long-Lived Access Tokens",
|
||||
"description": "Create long-lived access tokens to allow your scripts to interact with your Home Assistant instance. Each token will be valid for 10 years from creation. The following long-lived access tokens are currently active.",
|
||||
"learn_auth_requests": "Learn how to make authenticated requests.",
|
||||
"created_at": "Created at {date}",
|
||||
"confirm_delete": "Are you sure you want to delete the access token for {name}?",
|
||||
"delete_failed": "Failed to delete the access token.",
|
||||
"create": "Create Token",
|
||||
"create_failed": "Failed to create the access token.",
|
||||
"prompt_name": "Name?",
|
||||
"prompt_copy_token": "Copy your access token. It will not be shown again.",
|
||||
"empty_state": "You have no long-lived access tokens yet."
|
||||
}
|
||||
},
|
||||
"shopping-list": {
|
||||
|
9
src/util/fetch-with-auth.js
Normal file
9
src/util/fetch-with-auth.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export const fetchWithAuth = async (auth, input, init = {}) => {
|
||||
if (auth.expired) await auth.refreshAccessToken();
|
||||
init.credentials = 'same-origin';
|
||||
if (!init.headers) {
|
||||
init.headers = {};
|
||||
}
|
||||
init.headers.authorization = `Bearer ${auth.accessToken}`;
|
||||
return await fetch(input, init);
|
||||
};
|
@@ -1,52 +1,57 @@
|
||||
export default function hassCallApi(host, auth, method, path, parameters) {
|
||||
var url = host + '/api/' + path;
|
||||
import { fetchWithAuth } from './fetch-with-auth.js';
|
||||
|
||||
return new Promise(function (resolve, reject) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.open(method, url, true);
|
||||
req.setRequestHeader('authorization', `Bearer ${auth.accessToken}`);
|
||||
/* eslint-disable no-throw-literal */
|
||||
|
||||
req.onload = function () {
|
||||
let body = req.responseText;
|
||||
const contentType = req.getResponseHeader('content-type');
|
||||
export default async function hassCallApi(auth, method, path, parameters) {
|
||||
const url = `${auth.data.hassUrl}/api/${path}`;
|
||||
|
||||
if (contentType && contentType.indexOf('application/json') !== -1) {
|
||||
try {
|
||||
body = JSON.parse(req.responseText);
|
||||
} catch (err) {
|
||||
reject({
|
||||
error: 'Unable to parse JSON response',
|
||||
status_code: req.status,
|
||||
body: body,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const init = {
|
||||
method: method,
|
||||
headers: {},
|
||||
};
|
||||
|
||||
if (req.status > 199 && req.status < 300) {
|
||||
resolve(body);
|
||||
} else {
|
||||
reject({
|
||||
error: 'Response error: ' + req.status,
|
||||
status_code: req.status,
|
||||
body: body
|
||||
});
|
||||
}
|
||||
if (parameters) {
|
||||
init.headers['Content-Type'] = 'application/json;charset=UTF-8';
|
||||
init.body = JSON.stringify(parameters);
|
||||
}
|
||||
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetchWithAuth(auth, url, init);
|
||||
} catch (err) {
|
||||
throw {
|
||||
error: 'Request error',
|
||||
status_code: undefined,
|
||||
body: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
req.onerror = function () {
|
||||
reject({
|
||||
error: 'Request error',
|
||||
status_code: req.status,
|
||||
body: req.responseText,
|
||||
});
|
||||
};
|
||||
let body = null;
|
||||
|
||||
if (parameters) {
|
||||
req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
|
||||
req.send(JSON.stringify(parameters));
|
||||
} else {
|
||||
req.send();
|
||||
const contentType = response.headers.get('content-type');
|
||||
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
try {
|
||||
body = await response.json();
|
||||
} catch (err) {
|
||||
throw {
|
||||
error: 'Unable to parse JSON response',
|
||||
status_code: err.status,
|
||||
body: null,
|
||||
};
|
||||
}
|
||||
});
|
||||
} else {
|
||||
body = await response.text();
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw {
|
||||
error: `Response error: ${response.status}`,
|
||||
status_code: response.status,
|
||||
body: body
|
||||
};
|
||||
}
|
||||
|
||||
return body;
|
||||
}
|
||||
|
@@ -562,7 +562,8 @@
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Невалидно потребителско име или парола"
|
||||
"invalid_auth": "Невалидно потребителско име или парола",
|
||||
"invalid_code": "Невалиден код за аутентикация"
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Inicializuji",
|
||||
"authorizing_client": "Chystáte se dát {clientId} práva pro Home Assistant instanci.",
|
||||
"logging_in_with": "Přihlásit se pomocí ** {authProviderName} **.",
|
||||
"logging_in_with": "Přihlásit se pomocí **{authProviderName}**.",
|
||||
"pick_auth_provider": "Nebo se přihlaste s",
|
||||
"abort_intro": "Přihlášení bylo zrušeno",
|
||||
"form": {
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Initialiserer",
|
||||
"authorizing_client": "Du er ved at give {clientId} adgang til din Home Assistant-instans.",
|
||||
"logging_in_with": "Log ind med ** {authProviderName} **.",
|
||||
"logging_in_with": "Log ind med **{authProviderName}**.",
|
||||
"pick_auth_provider": "Eller log ind med",
|
||||
"abort_intro": "Login afbrudt",
|
||||
"form": {
|
||||
@@ -569,7 +569,7 @@
|
||||
"data": {
|
||||
"code": "To-faktor godkendelseskode"
|
||||
},
|
||||
"description": "Åbn ** {mfa_module_name} ** på din enhed for at se din to-faktor godkendelseskode og bekræft din identitet:"
|
||||
"description": "Åbn **{mfa_module_name}** på din enhed for at se din to-faktor godkendelseskode og bekræft din identitet:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -590,9 +590,9 @@
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "To faktor godkendelseskode"
|
||||
"code": "To-faktor godkendelseskode"
|
||||
},
|
||||
"description": "Åbn ** {mfa_module_name} ** på din enhed for at se din to-faktor godkendelseskode og bekræft din identitet:"
|
||||
"description": "Åbn **{mfa_module_name}** på din enhed for at se din to-faktor godkendelseskode og bekræft din identitet:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -601,7 +601,7 @@
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "Du har ikke konfigureret en API-adgangskode.",
|
||||
"login_expired": "Sessionen er udløbet, log venligst ind igen"
|
||||
"login_expired": "Session er udløbet, log ind igen."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -545,6 +545,27 @@
|
||||
"error_no_theme": "No themes available.",
|
||||
"link_promo": "Learn about themes",
|
||||
"dropdown_label": "Theme"
|
||||
},
|
||||
"refresh_tokens": {
|
||||
"header": "Refresh Tokens",
|
||||
"description": "Each refresh token represents a login session. Refresh tokens will be automatically removed when you click log out. Below a list of refresh tokens that are currently active for your account.",
|
||||
"token_title": "Refresh token for {clientId}",
|
||||
"created_at": "Created at {date}",
|
||||
"confirm_delete": "Are you sure you want to delete the refresh token for {name}?",
|
||||
"delete_failed": "Failed to delete the refresh token."
|
||||
},
|
||||
"long_lived_access_tokens": {
|
||||
"header": "Long-Lived Access Tokens",
|
||||
"description": "Create long-lived access tokens to allow your scripts to interact with your Home Assistant instance. Each token will be valid for 10 years from creation. The following long-lived access tokens are currently active.",
|
||||
"learn_auth_requests": "Learn how to make authenticated requests.",
|
||||
"created_at": "Created at {date}",
|
||||
"confirm_delete": "Are you sure you want to delete the access token for {name}?",
|
||||
"delete_failed": "Failed to delete the access token.",
|
||||
"create": "Create Token",
|
||||
"create_failed": "Failed to create the access token.",
|
||||
"prompt_name": "Name?",
|
||||
"prompt_copy_token": "Copy your access token. It will not be shown again.",
|
||||
"empty_state": "You have no long-lived access tokens yet."
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Inicializando",
|
||||
"authorizing_client": "Está por dar acceso a {clientId} a su instancia de Home Assistant.",
|
||||
"logging_in_with": "Iniciando sesión con ** {authProviderName} **.",
|
||||
"logging_in_with": "Iniciando sesión con **{authProviderName}**.",
|
||||
"pick_auth_provider": "O inicia sesión con",
|
||||
"abort_intro": "Inicio de sesión cancelado",
|
||||
"form": {
|
||||
@@ -590,18 +590,18 @@
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Código de autenticado de dos factores"
|
||||
"code": "Código de autenticación de dos factores"
|
||||
},
|
||||
"description": "Abra el **{mfa_module_name}** en su dispositivo para ver su código de autenticar de dos factores y verificar su identidad:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Contraseña API inválida",
|
||||
"invalid_code": "Código de autenticado invalido"
|
||||
"invalid_code": "Código de autenticación inválido"
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "No tienes una contraseña API configurada.",
|
||||
"login_expired": "La sesión ha expirado, por favor inicie sesión nuevamente."
|
||||
"login_expired": "La sesión expiró, por favor inicie sesión nuevamente."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Inicializando",
|
||||
"authorizing_client": "Está por dar acceso a {clientId} a su instancia de Home Assistant.",
|
||||
"logging_in_with": "Iniciando sesión con ** {authProviderName} **.",
|
||||
"logging_in_with": "Iniciando sesión con **{authProviderName}**.",
|
||||
"pick_auth_provider": "O inicia sesión con",
|
||||
"abort_intro": "Inicio de sesión cancelado",
|
||||
"form": {
|
||||
@@ -590,18 +590,18 @@
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Código de autenticar de dos factores"
|
||||
"code": "Código de autenticado de dos factores"
|
||||
},
|
||||
"description": "Abra el **{mfa_module_name}** en su dispositivo para ver su código autenticado de dos factores y verificar su identidad:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Contraseña de API inválida",
|
||||
"invalid_code": "Código de verificación invalido"
|
||||
"invalid_code": "Código de autenticación inválido"
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "No tienes una contraseña de API configurada.",
|
||||
"login_expired": "Sesión vencida, por favor iniciar sesión de nuevo."
|
||||
"login_expired": "La sesión expiró, por favor inicie sesión de nuevo."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -546,6 +546,95 @@
|
||||
"link_promo": "Lisateave teemade kohta",
|
||||
"dropdown_label": "Teema"
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
"initializing": "Lähtestan",
|
||||
"authorizing_client": "Kavatsed anda {clientId} jaoks juurdepääsu oma Home Assistant serverile.",
|
||||
"logging_in_with": "Login sisse **{authProviderName}** abil.",
|
||||
"pick_auth_provider": "Või logi sisse, kasutades",
|
||||
"abort_intro": "Sisselogimine katkestatud",
|
||||
"form": {
|
||||
"working": "Palun oota",
|
||||
"unknown_error": "Midagi läks valesti",
|
||||
"providers": {
|
||||
"homeassistant": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"username": "Kasutajanimi",
|
||||
"password": "Salasõna"
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Kaheastmeline autentimiskood"
|
||||
},
|
||||
"description": "Ava oma seadmes **{mfa_module_name}**, et näha oma kahetasemelise autentimise koodi ja tõendada oma isikut:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Vale kasutajanimi või salasõna",
|
||||
"invalid_code": "Vigane autentimiskood"
|
||||
},
|
||||
"abort": {
|
||||
"login_expired": "Sessioon aegus, palun logi uuesti sisse."
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "API salasõna"
|
||||
},
|
||||
"description": "Palun seadista http seadetes oma API salasõna:"
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Kaheastmeline autentimiskood"
|
||||
},
|
||||
"description": "Ava oma seadmes **{mfa_module_name}**, et näha oma kahetasemelise autentimise koodi ja tõendada oma isikut:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Vale API salasõna",
|
||||
"invalid_code": "Vigane autentimiskood"
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "Sul pole API salasõna seadistatud",
|
||||
"login_expired": "Sessioon aegus, palun logi uuesti sisse."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"user": "Kasutaja"
|
||||
},
|
||||
"description": "Palun vali kasutaja, kellena soovid sisse logida:"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"not_whitelisted": "Sinu arvuti ei ole lubatute nimekirjas."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page-onboarding": {
|
||||
"intro": "Kas oled valmis oma kodu ellu äratama, oma privaatsust tagasi võitma ja ühinema ülemaailmse nokitsejate kogukonnaga?",
|
||||
"user": {
|
||||
"intro": "Alustame kasutajakonto loomisega.",
|
||||
"required_field": "Nõutud",
|
||||
"data": {
|
||||
"name": "Nimi",
|
||||
"username": "Kasutajanimi",
|
||||
"password": "Salasõna"
|
||||
},
|
||||
"create_account": "Loo konto",
|
||||
"error": {
|
||||
"required_fields": "Täida kõik nõutud väljad"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
@@ -710,6 +799,11 @@
|
||||
"ask": "Kas soovid selle sisselogimise salvestada?",
|
||||
"decline": "Tänan ei",
|
||||
"confirm": "Salvesta sisselogimine"
|
||||
},
|
||||
"notification_drawer": {
|
||||
"click_to_configure": "{entity} seadistamiseks klõpsa nuppu",
|
||||
"empty": "Teavitusi pole",
|
||||
"title": "Teavitused"
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
|
@@ -569,7 +569,7 @@
|
||||
"data": {
|
||||
"code": "Code d'authentification à deux facteurs"
|
||||
},
|
||||
"description": "Ouvrez le ** {mfa_module_name} ** sur votre appareil pour afficher votre code d'authentification à deux facteurs et vérifier votre identité:"
|
||||
"description": "Ouvrez le **{mfa_module_name}** sur votre appareil pour afficher votre code d'authentification à deux facteurs et vérifier votre identité:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -592,7 +592,7 @@
|
||||
"data": {
|
||||
"code": "Code d'authentification à deux facteurs"
|
||||
},
|
||||
"description": "Ouvrez le ** {mfa_module_name} ** sur votre appareil pour afficher votre code d'authentification à deux facteurs et vérifier votre identité:"
|
||||
"description": "Ouvrez le **{mfa_module_name}** sur votre appareil pour afficher votre code d'authentification à deux facteurs et vérifier votre identité:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"config": "Konfigurasi",
|
||||
"states": "Ikhtisar",
|
||||
"map": "Peta",
|
||||
"logbook": "Buku Catatan",
|
||||
"logbook": "Catatan Log",
|
||||
"history": "Riwayat",
|
||||
"mailbox": "Kotak pesan",
|
||||
"shopping_list": "Daftar belanja",
|
||||
@@ -72,8 +72,8 @@
|
||||
"on": "Tidak aman"
|
||||
},
|
||||
"presence": {
|
||||
"off": "di luar",
|
||||
"on": "di rumah"
|
||||
"off": "Keluar",
|
||||
"on": "Rumah"
|
||||
},
|
||||
"battery": {
|
||||
"off": "Normal",
|
||||
@@ -164,7 +164,7 @@
|
||||
"locked": "Terkunci",
|
||||
"unlocked": "Terbuka",
|
||||
"ok": "OK",
|
||||
"problem": "Malasah"
|
||||
"problem": "Masalah"
|
||||
},
|
||||
"input_boolean": {
|
||||
"off": "Off",
|
||||
@@ -183,6 +183,7 @@
|
||||
"on": "On",
|
||||
"playing": "Memainkan",
|
||||
"paused": "Jeda",
|
||||
"idle": "Diam",
|
||||
"standby": "Siaga"
|
||||
},
|
||||
"plant": {
|
||||
@@ -231,6 +232,13 @@
|
||||
"snowy-rainy": "Bersalju, hujan",
|
||||
"sunny": "Cerah",
|
||||
"windy": "Berangin"
|
||||
},
|
||||
"vacuum": {
|
||||
"cleaning": "Membersihkan",
|
||||
"error": "Kesalahan",
|
||||
"idle": "Siaga",
|
||||
"paused": "Berhenti",
|
||||
"returning": "Kembali ke dock"
|
||||
}
|
||||
},
|
||||
"state_badge": {
|
||||
@@ -334,13 +342,15 @@
|
||||
"to": "Ke"
|
||||
},
|
||||
"homeassistant": {
|
||||
"label": "Home Assistant",
|
||||
"event": "Event:",
|
||||
"start": "Mulai",
|
||||
"shutdown": "Matikan"
|
||||
},
|
||||
"mqtt": {
|
||||
"label": "MQTT",
|
||||
"topic": "Topik"
|
||||
"topic": "Topik",
|
||||
"payload": "Payload (opsional)"
|
||||
},
|
||||
"numeric_state": {
|
||||
"label": "Status nomor",
|
||||
@@ -460,6 +470,122 @@
|
||||
"zwave": {
|
||||
"caption": "Z-Wave",
|
||||
"description": "Kelola jaringan Z-Wave anda"
|
||||
},
|
||||
"users": {
|
||||
"caption": "Pengguna",
|
||||
"description": "Kelola pengguna",
|
||||
"picker": {
|
||||
"title": "Pengguna"
|
||||
},
|
||||
"editor": {
|
||||
"rename_user": "Ubah nama pengguna",
|
||||
"change_password": "Ganti kata sandi",
|
||||
"activate_user": "Aktifkan pengguna",
|
||||
"deactivate_user": "Nonaktifkan pengguna",
|
||||
"delete_user": "Hapus pengguna"
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"push_notifications": {
|
||||
"header": "Pemberitahuan push",
|
||||
"description": "Kirim pemberitahuan ke perangkat ini.",
|
||||
"error_load_platform": "Konfigurasi notify.html5.",
|
||||
"error_use_https": "Membutuhkan SSL diaktifkan untuk frontend.",
|
||||
"push_notifications": "Pemberitahuan push",
|
||||
"link_promo": "Pelajari lebih lanjut"
|
||||
},
|
||||
"language": {
|
||||
"header": "Bahasa",
|
||||
"link_promo": "Bantu menerjemahkan",
|
||||
"dropdown_label": "Bahasa"
|
||||
},
|
||||
"themes": {
|
||||
"header": "Tema",
|
||||
"error_no_theme": "Tidak ada tema yang tersedia.",
|
||||
"link_promo": "Pelajari tentang tema",
|
||||
"dropdown_label": "Tema"
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
"initializing": "Inisialisasi",
|
||||
"authorizing_client": "Anda akan memberi akses {clientId} ke Home Assistant Anda.",
|
||||
"logging_in_with": "Login dengan **{authProviderName}**.",
|
||||
"pick_auth_provider": "Atau login dengan",
|
||||
"abort_intro": "Login dibatalkan",
|
||||
"form": {
|
||||
"working": "Mohon tunggu",
|
||||
"unknown_error": "Ada yang salah",
|
||||
"providers": {
|
||||
"homeassistant": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"username": "Nama pengguna",
|
||||
"password": "Kata sandi"
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Kode Autentikasi Dua Faktor"
|
||||
},
|
||||
"description": "Buka ** {mfa_module_name} ** pada perangkat Anda untuk melihat kode otentikasi dua-faktor Anda dan verifikasi identitas Anda:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Username dan password salah",
|
||||
"invalid_code": "Kode autentikasi tidak valid"
|
||||
},
|
||||
"abort": {
|
||||
"login_expired": "Sesi kedaluwarsa, harap masuk lagi."
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "Kata Sandi API"
|
||||
},
|
||||
"description": "Silakan masukkan kata sandi API di konfigurasi http Anda:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Kata sandi API tidak valid"
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "Anda tidak memiliki kata sandi API yang dikonfigurasi."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"user": "Pengguna"
|
||||
},
|
||||
"description": "Silakan pilih pengguna yang ingin login sebagai:"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"not_whitelisted": "Komputer Anda tidak masuk daftar putih."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page-onboarding": {
|
||||
"intro": "Apakah Anda siap untuk membuat rumah Anda lebih hidup, merebut kembali privasi Anda dan bergabung dengan komunitas tinkerers dunia?",
|
||||
"user": {
|
||||
"intro": "Mari kita mulai dengan membuat akun pengguna.",
|
||||
"required_field": "Wajib",
|
||||
"data": {
|
||||
"name": "Nama",
|
||||
"username": "Username",
|
||||
"password": "Kata sandi"
|
||||
},
|
||||
"create_account": "Buat Akun",
|
||||
"error": {
|
||||
"required_fields": "Isi semua bidang yang wajib diisi"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -515,6 +641,51 @@
|
||||
"w": "B"
|
||||
},
|
||||
"forecast": "Ramalan cuaca"
|
||||
},
|
||||
"alarm_control_panel": {
|
||||
"code": "Kode",
|
||||
"clear_code": "Hapus"
|
||||
},
|
||||
"automation": {
|
||||
"last_triggered": "Terakhir terpicu",
|
||||
"trigger": "Pemicu"
|
||||
},
|
||||
"cover": {
|
||||
"position": "Posisi",
|
||||
"tilt_position": "Posisi kemiringan"
|
||||
},
|
||||
"fan": {
|
||||
"speed": "Kecepatan",
|
||||
"direction": "Arah"
|
||||
},
|
||||
"light": {
|
||||
"brightness": "Kecerahan",
|
||||
"color_temperature": "Temperatur warna",
|
||||
"white_value": "Nilai putih",
|
||||
"effect": "Efek"
|
||||
},
|
||||
"climate": {
|
||||
"on_off": "Hidup \/ mati",
|
||||
"target_temperature": "Target suhu",
|
||||
"target_humidity": "Target kelembaban",
|
||||
"swing_mode": "Mode ayunan"
|
||||
},
|
||||
"lock": {
|
||||
"lock": "Kunci",
|
||||
"unlock": "Membuka"
|
||||
},
|
||||
"media_player": {
|
||||
"source": "Sumber",
|
||||
"sound_mode": "Mode suara"
|
||||
},
|
||||
"vacuum": {
|
||||
"actions": {
|
||||
"resume_cleaning": "Lanjutkan pembersihan",
|
||||
"return_to_base": "Kembali ke dock",
|
||||
"start_cleaning": "Mulai membersihkan",
|
||||
"turn_on": "Nyalakan",
|
||||
"turn_off": "Matikan"
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
@@ -527,6 +698,9 @@
|
||||
"service": "Layanan"
|
||||
},
|
||||
"relative_time": {
|
||||
"past": "lalu",
|
||||
"future": "dalam",
|
||||
"never": "Tak pernah",
|
||||
"duration": {
|
||||
"second": "{count} {count, plural,\n one {detik}\n other {detik}\n}",
|
||||
"minute": "{count} {count, plural,\n one {menit}\n other {menit}\n}",
|
||||
@@ -534,15 +708,35 @@
|
||||
"day": "{count} {count, plural,\n one {hari}\n other {hari}\n}",
|
||||
"week": "{count} {count, plural,\n one {minggu}\n other {minggu}\n}"
|
||||
}
|
||||
},
|
||||
"history_charts": {
|
||||
"loading_history": "Memuat riwayat status ...",
|
||||
"no_history_found": "Tidak ada riwayat status yang ditemukan."
|
||||
}
|
||||
},
|
||||
"notification_toast": {
|
||||
"entity_turned_on": "{entity} sudah dinyalakan.",
|
||||
"entity_turned_off": "{entity} sudah dimatikan.",
|
||||
"service_called": "Layanan {service} dipanggil.",
|
||||
"service_call_failed": "Gagal memanggil layanan {service} .",
|
||||
"connection_lost": "Koneksi terputus. Menghubungan kembali ..."
|
||||
},
|
||||
"dialogs": {
|
||||
"more_info_settings": {
|
||||
"save": "Simpan",
|
||||
"name": "Nama",
|
||||
"entity_id": "Entiti ID"
|
||||
}
|
||||
},
|
||||
"auth_store": {
|
||||
"ask": "Apakah Anda ingin menyimpan login ini?",
|
||||
"decline": "Tidak, terima kasih",
|
||||
"confirm": "Simpan login"
|
||||
},
|
||||
"notification_drawer": {
|
||||
"click_to_configure": "Klik tombol untuk mengonfigurasi {entity}",
|
||||
"empty": "Tidak ada pemberitahuan",
|
||||
"title": "Pemberitahuan"
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
@@ -577,7 +771,8 @@
|
||||
"sun": "Matahari",
|
||||
"switch": "Sakelar",
|
||||
"updater": "Updater",
|
||||
"zwave": "Z-Wave"
|
||||
"zwave": "Z-Wave",
|
||||
"vacuum": "Vakum"
|
||||
},
|
||||
"attribute": {
|
||||
"weather": {
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Inizializzazione",
|
||||
"authorizing_client": "Stai per dare accesso {clientId} alla tua istanza di Assistente Home.",
|
||||
"logging_in_with": "Accesso con ** {authProviderName} **.",
|
||||
"logging_in_with": "Accesso con **{authProviderName}**.",
|
||||
"pick_auth_provider": "Oppure accedi con",
|
||||
"abort_intro": "Login interrotto",
|
||||
"form": {
|
||||
@@ -569,7 +569,7 @@
|
||||
"data": {
|
||||
"code": "Codice di autenticazione a due fattori"
|
||||
},
|
||||
"description": "Apri ** {mfa_module_name} ** sul tuo dispositivo per visualizzare il codice di autenticazione a due fattori e verificare la tua identità:"
|
||||
"description": "Apri il **{mfa_module_name}** sul tuo dispositivo per visualizzare il codice di autenticazione a due fattori e verificare la tua identità:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -592,7 +592,7 @@
|
||||
"data": {
|
||||
"code": "Codice di autenticazione a due fattori"
|
||||
},
|
||||
"description": "Apri il **{mfa_module_name}** sul tuo dispositivo per vedere il codice di autenticazione a due fattori (2FA) e verifica la tua identità:"
|
||||
"description": "Apri il **{mfa_module_name}** sul tuo dispositivo per visualizzare il codice di autenticazione a due fattori e verificare la tua identità:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -601,7 +601,7 @@
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "Non hai una password API configurata.",
|
||||
"login_expired": "Sessione scaduta, effettua nuovamente l'accesso."
|
||||
"login_expired": "Sessione scaduta, effettua nuovamente il login."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -569,7 +569,7 @@
|
||||
"data": {
|
||||
"code": "2 단계 인증 코드"
|
||||
},
|
||||
"description": "2 단계 인증 코드 및 신원을 확인하기 위해 기기에서 ** {mfa_module_name} ** 을(를) 열어주세요:"
|
||||
"description": "2 단계 인증 코드 및 신원을 확인하기 위해 기기에서 **{mfa_module_name}** 을(를) 열어주세요:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -586,13 +586,13 @@
|
||||
"data": {
|
||||
"password": "API 비밀번호"
|
||||
},
|
||||
"description": "http config 에 API 암호를 입력해주세요:"
|
||||
"description": "configuration.yaml 에 설정한 api_password 를 입력해주세요:"
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "2 단계 인증 코드"
|
||||
},
|
||||
"description": "2 단계 인증 코드 및 신원을 확인하기 위해 기기에서 ** {mfa_module_name} ** 을(를) 열어주세요:"
|
||||
"description": "2 단계 인증 코드 및 신원을 확인하기 위해 기기에서 **{mfa_module_name}** 을(를) 열어주세요:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -600,8 +600,8 @@
|
||||
"invalid_code": "잘못된 인증 코드"
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "API 비밀번호를 구성하지 않았습니다.",
|
||||
"login_expired": "세션이 만료되었습니다. 다시 로그인 해주세요"
|
||||
"no_api_password_set": "API 비밀번호를 설정하지 않았습니다.",
|
||||
"login_expired": "세션이 만료되었습니다. 다시 로그인 해주세요."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -64,9 +64,12 @@
|
||||
"not_home": "Prom"
|
||||
},
|
||||
"media_player": {
|
||||
"off": "Izslēgts",
|
||||
"on": "Ieslēgts",
|
||||
"playing": "Atskaņo",
|
||||
"paused": "Apturēts",
|
||||
"idle": "Dīkstāvē"
|
||||
"idle": "Dīkstāvē",
|
||||
"standby": "Gaidīšanas režīmā"
|
||||
},
|
||||
"plant": {
|
||||
"ok": "Labi",
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Initialiserer",
|
||||
"authorizing_client": "Du er i ferd med å gi {clientId} tilgang til din Home Assistant",
|
||||
"logging_in_with": "Logg inn med ** {authProviderName} **.",
|
||||
"logging_in_with": "Logg inn med **{authProviderName}**.",
|
||||
"pick_auth_provider": "Eller logg inn med",
|
||||
"abort_intro": "Innlogging avbrutt",
|
||||
"form": {
|
||||
@@ -569,7 +569,7 @@
|
||||
"data": {
|
||||
"code": "To-faktor autentiseringskode"
|
||||
},
|
||||
"description": "Åpne ** {mfa_module_name} ** på enheten din for å se din tofaktors autentiseringskode og bekrefte identiteten din:"
|
||||
"description": "Åpne **{mfa_module_name}** på enheten din for å se din tofaktors autentiseringskode og bekrefte identiteten din:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -592,7 +592,7 @@
|
||||
"data": {
|
||||
"code": "To-faktor autentiseringskode"
|
||||
},
|
||||
"description": "Åpne **{mfa_module_name}** på din enhet for å vise to-faktor autentiseringkode og vertifiser din identitet."
|
||||
"description": "Åpne **{mfa_module_name}** på enheten din for å se din tofaktors autentiseringskode og bekrefte identiteten din:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -601,7 +601,7 @@
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "Du har ikke konfigurert et API-passord.",
|
||||
"login_expired": "Sesjonen har utløpt, vennligst logg inn igjen: "
|
||||
"login_expired": "Økten er utløpt, vennligst logg inn på nytt"
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -18,7 +18,7 @@
|
||||
"state": {
|
||||
"default": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony",
|
||||
"on": "Włączony",
|
||||
"unknown": "nieznany",
|
||||
"unavailable": "niedostępny"
|
||||
},
|
||||
@@ -41,7 +41,7 @@
|
||||
"binary_sensor": {
|
||||
"default": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"on": "Włączony"
|
||||
},
|
||||
"moisture": {
|
||||
"off": "sucho",
|
||||
@@ -122,7 +122,7 @@
|
||||
},
|
||||
"calendar": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"on": "Włączony"
|
||||
},
|
||||
"camera": {
|
||||
"recording": "nagrywanie",
|
||||
@@ -131,7 +131,7 @@
|
||||
},
|
||||
"climate": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony",
|
||||
"on": "Włączony",
|
||||
"heat": "ogrzewanie",
|
||||
"cool": "chłodzenie",
|
||||
"idle": "nieaktywny",
|
||||
@@ -162,7 +162,7 @@
|
||||
},
|
||||
"fan": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"on": "Włączony"
|
||||
},
|
||||
"group": {
|
||||
"off": "wyłączony",
|
||||
@@ -181,7 +181,7 @@
|
||||
},
|
||||
"input_boolean": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"on": "Włączony"
|
||||
},
|
||||
"light": {
|
||||
"off": "wyłączony",
|
||||
@@ -193,7 +193,7 @@
|
||||
},
|
||||
"media_player": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony",
|
||||
"on": "Włączony",
|
||||
"playing": "odtwarzanie",
|
||||
"paused": "pauza",
|
||||
"idle": "nieaktywny",
|
||||
@@ -205,37 +205,37 @@
|
||||
},
|
||||
"remote": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"on": "Włączony"
|
||||
},
|
||||
"scene": {
|
||||
"scening": "sceny"
|
||||
},
|
||||
"script": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"on": "Włączony"
|
||||
},
|
||||
"sensor": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"on": "Włączony"
|
||||
},
|
||||
"sun": {
|
||||
"above_horizon": "powyżej horyzontu",
|
||||
"below_horizon": "poniżej horyzontu"
|
||||
"above_horizon": "Powyżej horyzontu",
|
||||
"below_horizon": "Poniżej horyzontu"
|
||||
},
|
||||
"switch": {
|
||||
"off": "wyłączony",
|
||||
"on": "włączony"
|
||||
"off": "Wyłączony",
|
||||
"on": "Włączony"
|
||||
},
|
||||
"zwave": {
|
||||
"default": {
|
||||
"initializing": "inicjalizacja",
|
||||
"dead": "martwy",
|
||||
"sleeping": "uśpiony",
|
||||
"ready": "gotowy"
|
||||
"initializing": "Inicjalizacja",
|
||||
"dead": "Nieaktywny",
|
||||
"sleeping": "Uśpiony",
|
||||
"ready": "Gotowy"
|
||||
},
|
||||
"query_stage": {
|
||||
"initializing": "inicjalizacja ({query_stage})",
|
||||
"dead": "martwy ({query_stage})"
|
||||
"dead": "Nieaktywny ({query_stage})"
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Inicjowanie",
|
||||
"authorizing_client": "Czy na pewno chcesz dać dostęp {clientId} do Twojej instancji Home Assistant.",
|
||||
"logging_in_with": "Logowanie za pomocą ** {authProviderName} **.",
|
||||
"logging_in_with": "Logowanie za pomocą **{authProviderName}**.",
|
||||
"pick_auth_provider": "Lub zaloguj się za pomocą",
|
||||
"abort_intro": "Logowanie przerwane",
|
||||
"form": {
|
||||
@@ -569,7 +569,7 @@
|
||||
"data": {
|
||||
"code": "Dwuskładnikowy kod uwierzytelniający"
|
||||
},
|
||||
"description": "Otwórz ** {mfa_module_name} ** na urządzeniu, aby wyświetlić dwuskładnikowy kod uwierzytelniający i zweryfikować swoją tożsamość:"
|
||||
"description": "Otwórz **{mfa_module_name}** na urządzeniu, aby wyświetlić dwuskładnikowy kod uwierzytelniający i zweryfikować swoją tożsamość:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -592,7 +592,7 @@
|
||||
"data": {
|
||||
"code": "Dwuskładnikowy kod uwierzytelniający"
|
||||
},
|
||||
"description": "Otwórz ** {mfa_module_name} ** na urządzeniu, aby wyświetlić dwuskładnikowy kod uwierzytelniający i zweryfikować swoją tożsamość:"
|
||||
"description": "Otwórz **{mfa_module_name}** na urządzeniu, aby wyświetlić dwuskładnikowy kod uwierzytelniający i zweryfikować swoją tożsamość:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -610,7 +610,7 @@
|
||||
"data": {
|
||||
"user": "Użytkownik"
|
||||
},
|
||||
"description": "Proszę wybrać użytkownika, na którego chcesz się zalogować jako:"
|
||||
"description": "Proszę wybrać użytkownika, na którego chcesz się zalogować:"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
@@ -621,7 +621,7 @@
|
||||
}
|
||||
},
|
||||
"page-onboarding": {
|
||||
"intro": "Czy jesteś gotowy, aby obudzić swój dom, odzyskać prywatność i dołączyć do światowej społeczności majsterkowiczów?",
|
||||
"intro": "Czy jesteś gotowy, aby ożywić swój dom, odzyskać prywatność i dołączyć do światowej społeczności majsterkowiczów?",
|
||||
"user": {
|
||||
"intro": "Zacznijmy od utworzenia konta użytkownika.",
|
||||
"required_field": "Wymagane",
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Iniciando",
|
||||
"authorizing_client": "Você está prestes a dar acesso pra {clientId} à sua instância do Home Assistant.",
|
||||
"logging_in_with": "Fazendo login com ** {authProviderName} **.",
|
||||
"logging_in_with": "Fazendo login com **{authProviderName}**.",
|
||||
"pick_auth_provider": "Ou entre com",
|
||||
"abort_intro": "Login cancelado",
|
||||
"form": {
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "A inicializar",
|
||||
"authorizing_client": "Você está prestes a dar acesso a {clientId} à sua instância do Home Assistant.",
|
||||
"logging_in_with": "Fazendo login com ** {authProviderName} **.",
|
||||
"logging_in_with": "Fazendo login com **{authProviderName}**.",
|
||||
"pick_auth_provider": "Ou faça login com",
|
||||
"abort_intro": "Login abortado",
|
||||
"form": {
|
||||
|
@@ -526,11 +526,53 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"profile": {
|
||||
"push_notifications": {
|
||||
"header": "Notificări",
|
||||
"description": "Trimiteți notificări către acest dispozitiv.",
|
||||
"error_load_platform": "Configurați notify.html5.",
|
||||
"error_use_https": "Necesită SSL activat pentru interfaţă.",
|
||||
"push_notifications": "Notificări",
|
||||
"link_promo": "Aflați mai multe"
|
||||
},
|
||||
"language": {
|
||||
"header": "Limba",
|
||||
"link_promo": "Ajută la traducere",
|
||||
"dropdown_label": "Limba"
|
||||
},
|
||||
"themes": {
|
||||
"header": "Temă",
|
||||
"error_no_theme": "Nu există teme disponibile.",
|
||||
"link_promo": "Aflați mai multe despre teme",
|
||||
"dropdown_label": "Temă"
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
"initializing": "Inițializează",
|
||||
"logging_in_with": "Conectare cu **{authProviderName}**.",
|
||||
"pick_auth_provider": "Sau conectați-vă cu",
|
||||
"abort_intro": "Conectare intrerupta",
|
||||
"form": {
|
||||
"working": "Te rog așteaptă",
|
||||
"unknown_error": "Ceva n-a mers bine",
|
||||
"providers": {
|
||||
"homeassistant": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"username": "Nume de utilizator",
|
||||
"password": "Parola"
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Autentificare cu doi factori"
|
||||
},
|
||||
"description": "Deschideti **{mfa_module_name}** pe dispozitivul d-voastra pentru a vedea codul de 'two-factor authentication' si a va verifica indentitatea:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "nume de utilizator sau parola incorecte",
|
||||
"invalid_code": "Codul 'Two-factor Authentication' invalid"
|
||||
},
|
||||
"abort": {
|
||||
@@ -540,31 +582,57 @@
|
||||
"legacy_api_password": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "Parola API"
|
||||
},
|
||||
"description": "Introduceti parola API in http config:"
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Codul 'Two-factor Authentication'"
|
||||
"code": "Autentificare cu doi factori"
|
||||
},
|
||||
"description": "Deschideti **{mfa_module_name}** pe dispozitivul d-voastra pentru a vedea codul de 'two-factor authentication' si a va verifica indentitatea:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Cod de autentificare invalid"
|
||||
"invalid_auth": "Parola API nevalidă",
|
||||
"invalid_code": "Codul 'Two-factor Authentication' invalid"
|
||||
},
|
||||
"abort": {
|
||||
"login_expired": "Sesiune expirata, e nevoie sa va logati din nou."
|
||||
"no_api_password_set": "Nu aveți o parolă API configurată.",
|
||||
"login_expired": "Sesiunea a expirat, va rugam logati-va din nou."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"user": "Utilizator"
|
||||
},
|
||||
"description": "Selectati utilizatorul cu care doriti sa va logati:"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"not_whitelisted": "Calculatorul dvs. nu este pe lista albă."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"page-onboarding": {
|
||||
"user": {
|
||||
"intro": "Să începem prin crearea unui cont de utilizator.",
|
||||
"required_field": "Necesar",
|
||||
"data": {
|
||||
"name": "Nume",
|
||||
"username": "Nume de utilizator",
|
||||
"password": "Parola"
|
||||
},
|
||||
"create_account": "Creează cont",
|
||||
"error": {
|
||||
"required_fields": "Completați toate câmpurile obligatorii"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
@@ -724,6 +792,16 @@
|
||||
"name": "Nume",
|
||||
"entity_id": "ID-ul entității"
|
||||
}
|
||||
},
|
||||
"auth_store": {
|
||||
"ask": "Doriți să salvați aceste date de conectare?",
|
||||
"decline": "Nu, mulţumesc",
|
||||
"confirm": "Salvați datele de conectare"
|
||||
},
|
||||
"notification_drawer": {
|
||||
"click_to_configure": "Faceți clic pe buton pentru a configura {entity}",
|
||||
"empty": "Nicio notificare",
|
||||
"title": "Notificări"
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
|
@@ -319,7 +319,7 @@
|
||||
"introduction": "Изменение конфигурации может быть утомительным процессом. Мы знаем. Этот раздел попытается сделать вашу жизнь немного легче.",
|
||||
"validation": {
|
||||
"heading": "Проверка конфигурации",
|
||||
"introduction": "Проверьте свою конфигурацию, если вы внесли в нее некоторые изменения и хотите убедиться, что она действительна",
|
||||
"introduction": "Проверьте свою конфигурацию, если вы внесли в нее некоторые изменения и хотите убедиться в ее работоспособности",
|
||||
"check_config": "Проверить конфигурацию",
|
||||
"valid": "Конфигурация выполнена верно!",
|
||||
"invalid": "Ошибка в конфигурации"
|
||||
@@ -504,7 +504,7 @@
|
||||
}
|
||||
},
|
||||
"script": {
|
||||
"caption": "Скрипт",
|
||||
"caption": "Скрипты",
|
||||
"description": "Создавайте и редактируйте скрипты"
|
||||
},
|
||||
"zwave": {
|
||||
@@ -782,8 +782,8 @@
|
||||
}
|
||||
},
|
||||
"notification_toast": {
|
||||
"entity_turned_on": "{entity} включение.",
|
||||
"entity_turned_off": "{entity} выключение.",
|
||||
"entity_turned_on": "{entity} включается",
|
||||
"entity_turned_off": "{entity} выключается",
|
||||
"service_called": "Вызов службы {service}.",
|
||||
"service_call_failed": "Не удалось вызвать службу {service}.",
|
||||
"connection_lost": "Соединение потеряно. Переподключение ..."
|
||||
|
@@ -550,7 +550,7 @@
|
||||
"page-authorize": {
|
||||
"initializing": "Inicializacija",
|
||||
"authorizing_client": "Dali boste {clientId} dostop do vašega Home assistenta.",
|
||||
"logging_in_with": "Prijava z ** {authProviderName} **.",
|
||||
"logging_in_with": "Prijava z **{authProviderName}**.",
|
||||
"pick_auth_provider": "Ali se prijavite z",
|
||||
"abort_intro": "Prijava prekinjena",
|
||||
"form": {
|
||||
@@ -569,7 +569,7 @@
|
||||
"data": {
|
||||
"code": "Dvofaktorska koda za avtorizacijo"
|
||||
},
|
||||
"description": "V svoji napravi odprite ** {mfa_module_name} **, da si ogledate svojo dvofaktorsko kodo za preverjanje pristnosti in preverite svojo identiteto:"
|
||||
"description": "V svoji napravi odprite **{mfa_module_name}**, da si ogledate svojo dvofaktorsko kodo za preverjanje pristnosti in preverite svojo identiteto:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -590,9 +590,9 @@
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Dvofaktorska avtorizacijska koda"
|
||||
"code": "Dvofaktorska koda za avtorizacijo"
|
||||
},
|
||||
"description": "Odprite **{mfa_module_name}** na vaši napravi za ogled dvofaktorske avtorizacijske kode in potrdite vašo identiteto:"
|
||||
"description": "V svoji napravi odprite **{mfa_module_name}**, da si ogledate svojo dvofaktorsko kodo za preverjanje pristnosti in preverite svojo identiteto:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -601,7 +601,7 @@
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "Nimate nastavljenega gesla API-ja.",
|
||||
"login_expired": "Seja je potekla, prosim ponovno se prijavite."
|
||||
"login_expired": "Seja je potekla, prosimo, prijavite se znova."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
|
@@ -549,10 +549,10 @@
|
||||
},
|
||||
"page-authorize": {
|
||||
"initializing": "Initierar",
|
||||
"authorizing_client": "Du håller på att ge {clientid} behörighet till din Home Assistant.",
|
||||
"authorizing_client": "Du håller på att ge {clientId} behörighet till din Home Assistant.",
|
||||
"logging_in_with": "Loggar in med **{authProviderName}**.",
|
||||
"pick_auth_provider": "Eller logga in med",
|
||||
"abort_intro": "Login avbruten",
|
||||
"abort_intro": "Inloggning avbruten",
|
||||
"form": {
|
||||
"working": "Vänligen vänta",
|
||||
"unknown_error": "Något gick fel",
|
||||
@@ -568,7 +568,8 @@
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Tvåfaktorsautentiseringskod"
|
||||
}
|
||||
},
|
||||
"description": "Öppna **{mfa_module_name}** på din enhet för att visa din tvåfaktors autentiseringskod och verifiera din identitet:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
@@ -589,18 +590,18 @@
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "Tvåfaktorsautentiseringskod:"
|
||||
"code": "Tvåfaktorsautentiseringskod"
|
||||
},
|
||||
"description": "Öppna **(mfa_module_name)** på din enhet för att se din tvåfaktorsautentiseringskod och verifiera din identitet:"
|
||||
"description": "Öppna **{mfa_module_name}** på din enhet för att visa din tvåfaktors autentiseringskod och verifiera din identitet:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "Ogiltigt API-lösenord",
|
||||
"invalid_code": "Felaktig autentiserings kod"
|
||||
"invalid_code": "Ogiltig autentiseringskod"
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "Du har inget API lösenord konfigurerat",
|
||||
"login_expired": "Sessionen avslutades, logga in igen."
|
||||
"login_expired": "Sessionen avslutades. Logga in igen."
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
@@ -620,6 +621,7 @@
|
||||
}
|
||||
},
|
||||
"page-onboarding": {
|
||||
"intro": "Är du redo att väcka ditt hem, återta din integritet och gå med i en världsomspännande gemenskap av hemmafixare?",
|
||||
"user": {
|
||||
"intro": "Låt oss börja med att skapa ett användarkonto",
|
||||
"required_field": "Krävs",
|
||||
@@ -628,7 +630,10 @@
|
||||
"username": "Användarnamn",
|
||||
"password": "Lösenord"
|
||||
},
|
||||
"create_account": "Skapa konto"
|
||||
"create_account": "Skapa konto",
|
||||
"error": {
|
||||
"required_fields": "Fyll i alla fält som krävs"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -797,7 +802,7 @@
|
||||
},
|
||||
"notification_drawer": {
|
||||
"click_to_configure": "Klicka på knappen för att konfigurera {entity}",
|
||||
"empty": "Ingra notiser",
|
||||
"empty": "Inga notiser",
|
||||
"title": "Notiser"
|
||||
}
|
||||
},
|
||||
|
@@ -24,7 +24,7 @@
|
||||
},
|
||||
"alarm_control_panel": {
|
||||
"armed": "Vũ trang",
|
||||
"disarmed": "Giải giáp",
|
||||
"disarmed": "Vô hiệu hóa",
|
||||
"armed_home": "Vũ trang khi ở nhà",
|
||||
"armed_away": "Vũ trang khi đi vắng",
|
||||
"armed_night": "Vũ trang đêm",
|
||||
@@ -248,7 +248,7 @@
|
||||
},
|
||||
"alarm_control_panel": {
|
||||
"armed": "VT",
|
||||
"disarmed": "Giải giáp",
|
||||
"disarmed": "Vô hiệu hoá",
|
||||
"pending": "Chờ",
|
||||
"arming": "Đang VT",
|
||||
"disarming": "Giải giáp"
|
||||
@@ -459,6 +459,19 @@
|
||||
"delete_user": "Xóa người dùng"
|
||||
}
|
||||
}
|
||||
},
|
||||
"page-authorize": {
|
||||
"form": {
|
||||
"providers": {
|
||||
"trusted_networks": {
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Vui lòng lựa chọn một tài khoản để đăng nhập"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
@@ -526,8 +539,8 @@
|
||||
"code": "Mã số",
|
||||
"clear_code": "Xóa",
|
||||
"disarm": "Không vũ trang",
|
||||
"arm_home": "VT Ở nhà",
|
||||
"arm_away": "VT Đi vắng"
|
||||
"arm_home": "An ninh khi ở nhà",
|
||||
"arm_away": "An ninh khi đi vắng"
|
||||
},
|
||||
"automation": {
|
||||
"last_triggered": "Kích hoạt lần cuối",
|
||||
@@ -611,7 +624,7 @@
|
||||
}
|
||||
},
|
||||
"domain": {
|
||||
"alarm_control_panel": "Bảng điều khiển báo động",
|
||||
"alarm_control_panel": "Bảng điều khiển an ninh",
|
||||
"automation": "Tự động hóa",
|
||||
"binary_sensor": "Cảm biến nhị phân",
|
||||
"calendar": "Lịch",
|
||||
|
@@ -564,10 +564,20 @@
|
||||
"username": "使用者名稱",
|
||||
"password": "使用者密碼"
|
||||
}
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "兩步驟驗證碼"
|
||||
},
|
||||
"description": "開啟裝置上的 **{mfa_module_name}** 以獲得兩步驟驗證碼,並進行驗證:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "使用者名稱或密碼無效"
|
||||
"invalid_auth": "使用者名稱或密碼無效",
|
||||
"invalid_code": "驗證碼無效"
|
||||
},
|
||||
"abort": {
|
||||
"login_expired": "登入階段已逾時,請重新登錄。"
|
||||
}
|
||||
},
|
||||
"legacy_api_password": {
|
||||
@@ -575,14 +585,23 @@
|
||||
"init": {
|
||||
"data": {
|
||||
"password": "API 密碼"
|
||||
}
|
||||
},
|
||||
"description": "請輸入 Http 設定中的 API 密鑰:"
|
||||
},
|
||||
"mfa": {
|
||||
"data": {
|
||||
"code": "兩步驟驗證碼"
|
||||
},
|
||||
"description": "開啟裝置上的 **{mfa_module_name}** 以獲得兩步驟驗證碼,並進行驗證:"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "API 密碼無效"
|
||||
"invalid_auth": "API 密碼無效",
|
||||
"invalid_code": "驗證碼無效"
|
||||
},
|
||||
"abort": {
|
||||
"no_api_password_set": "尚未設定 API 密碼。"
|
||||
"no_api_password_set": "尚未設定 API 密碼。",
|
||||
"login_expired": "登入階段已逾時,請重新登錄。"
|
||||
}
|
||||
},
|
||||
"trusted_networks": {
|
||||
@@ -590,7 +609,8 @@
|
||||
"init": {
|
||||
"data": {
|
||||
"user": "使用者"
|
||||
}
|
||||
},
|
||||
"description": "請選擇所要登錄的用戶:"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
|
@@ -6,6 +6,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const WorkboxPlugin = require('workbox-webpack-plugin');
|
||||
const CompressionPlugin = require("compression-webpack-plugin");
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const zopfli = require('@gfx/zopfli');
|
||||
const translationMetadata = require('./build-translations/translationMetadata.json');
|
||||
|
||||
const version = fs.readFileSync('setup.py', 'utf8').match(/\d{8}[^']*/);
|
||||
@@ -138,7 +139,10 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
/\.LICENSE$/,
|
||||
/\.py$/,
|
||||
/\.txt$/,
|
||||
]
|
||||
],
|
||||
algorithm(input, compressionOptions, callback) {
|
||||
return zopfli.gzip(input, compressionOptions, callback);
|
||||
},
|
||||
}),
|
||||
new WorkboxPlugin.InjectManifest({
|
||||
swSrc: './src/entrypoints/service-worker-bootstrap.js',
|
||||
@@ -151,32 +155,13 @@ function createConfig(isProdBuild, latestBuild) {
|
||||
/hass-icons.js$/,
|
||||
/\.chunk\.js$/,
|
||||
],
|
||||
// Static assets get cached during runtime. But these we want to explicitly cache
|
||||
// Need to be done using templatedUrls because prefix is /static
|
||||
globDirectory: '.',
|
||||
globIgnores: [],
|
||||
modifyUrlPrefix: {
|
||||
'hass_frontend': '/static'
|
||||
},
|
||||
templatedUrls: {
|
||||
[`/static/translations/${translationMetadata['translations']['en']['fingerprints']['en']}`]: [
|
||||
'build-translations/output/en.json'
|
||||
],
|
||||
'/static/icons/favicon-192x192.png': [
|
||||
'public/icons/favicon-192x192.png'
|
||||
],
|
||||
'/static/fonts/roboto/Roboto-Light.ttf': [
|
||||
'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Light.ttf'
|
||||
],
|
||||
'/static/fonts/roboto/Roboto-Medium.ttf': [
|
||||
'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Medium.ttf'
|
||||
],
|
||||
'/static/fonts/roboto/Roboto-Regular.ttf': [
|
||||
'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Regular.ttf'
|
||||
],
|
||||
'/static/fonts/roboto/Roboto-Bold.ttf': [
|
||||
'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Bold.ttf'
|
||||
],
|
||||
[`/static/translations/${translationMetadata['translations']['en']['fingerprints']['en']}`]: 'build-translations/output/en.json',
|
||||
'/static/icons/favicon-192x192.png': 'public/icons/favicon-192x192.png',
|
||||
'/static/fonts/roboto/Roboto-Light.ttf': 'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Light.ttf',
|
||||
'/static/fonts/roboto/Roboto-Medium.ttf': 'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Medium.ttf',
|
||||
'/static/fonts/roboto/Roboto-Regular.ttf': 'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Regular.ttf',
|
||||
'/static/fonts/roboto/Roboto-Bold.ttf': 'node_modules/@polymer/font-roboto-local/fonts/roboto/Roboto-Bold.ttf',
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
|
135
yarn.lock
135
yarn.lock
@@ -577,6 +577,12 @@
|
||||
lodash "^4.17.5"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@gfx/zopfli@^1.0.8":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@gfx/zopfli/-/zopfli-1.0.8.tgz#4bad09d5c8bd8156e018716228e9d84e2347b3f0"
|
||||
dependencies:
|
||||
base64-js "^1.0.0"
|
||||
|
||||
"@mdi/svg@^2.4.85":
|
||||
version "2.7.94"
|
||||
resolved "https://registry.yarnpkg.com/@mdi/svg/-/svg-2.7.94.tgz#5f2b03c363b10f7e4cf8c8a5fdc81cbd838bf44b"
|
||||
@@ -1953,6 +1959,10 @@ agent-base@2:
|
||||
extend "~3.0.0"
|
||||
semver "~5.0.1"
|
||||
|
||||
ajv-errors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.0.tgz#ecf021fa108fd17dfb5e6b383f2dd233e31ffc59"
|
||||
|
||||
ajv-keywords@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762"
|
||||
@@ -3135,7 +3145,7 @@ base64-js@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1"
|
||||
|
||||
base64-js@^1.0.2:
|
||||
base64-js@^1.0.0, base64-js@^1.0.2:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
|
||||
|
||||
@@ -3492,7 +3502,7 @@ bytes@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
|
||||
|
||||
cacache@^10.0.1, cacache@^10.0.4:
|
||||
cacache@^10.0.4:
|
||||
version "10.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460"
|
||||
dependencies:
|
||||
@@ -3510,6 +3520,25 @@ cacache@^10.0.1, cacache@^10.0.4:
|
||||
unique-filename "^1.1.0"
|
||||
y18n "^4.0.0"
|
||||
|
||||
cacache@^11.2.0:
|
||||
version "11.2.0"
|
||||
resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.2.0.tgz#617bdc0b02844af56310e411c0878941d5739965"
|
||||
dependencies:
|
||||
bluebird "^3.5.1"
|
||||
chownr "^1.0.1"
|
||||
figgy-pudding "^3.1.0"
|
||||
glob "^7.1.2"
|
||||
graceful-fs "^4.1.11"
|
||||
lru-cache "^4.1.3"
|
||||
mississippi "^3.0.0"
|
||||
mkdirp "^0.5.1"
|
||||
move-concurrently "^1.0.1"
|
||||
promise-inflight "^1.0.1"
|
||||
rimraf "^2.6.2"
|
||||
ssri "^6.0.0"
|
||||
unique-filename "^1.1.0"
|
||||
y18n "^4.0.0"
|
||||
|
||||
cache-base@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
|
||||
@@ -4004,13 +4033,14 @@ compressible@~2.0.14:
|
||||
dependencies:
|
||||
mime-db ">= 1.34.0 < 2"
|
||||
|
||||
compression-webpack-plugin@^1.1.11:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-1.1.11.tgz#8384c7a6ead1d2e2efb190bdfcdcf35878ed8266"
|
||||
compression-webpack-plugin@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/compression-webpack-plugin/-/compression-webpack-plugin-2.0.0.tgz#46476350c1eb27f783dccc79ac2f709baa2cffbc"
|
||||
dependencies:
|
||||
cacache "^10.0.1"
|
||||
find-cache-dir "^1.0.0"
|
||||
cacache "^11.2.0"
|
||||
find-cache-dir "^2.0.0"
|
||||
neo-async "^2.5.0"
|
||||
schema-utils "^1.0.0"
|
||||
serialize-javascript "^1.4.0"
|
||||
webpack-sources "^1.0.1"
|
||||
|
||||
@@ -5449,6 +5479,10 @@ fecha@^2.3.3, "fecha@https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ec
|
||||
version "2.3.3"
|
||||
resolved "https://github.com/balloob/fecha/archive/51d14fd0eb4781e2ecf265d1c3080706259133b5.tar.gz#bfb1e49121dd7821601af35faf4fe93dbd19200a"
|
||||
|
||||
figgy-pudding@^3.1.0, figgy-pudding@^3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790"
|
||||
|
||||
figures@^1.3.5:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
|
||||
@@ -5528,6 +5562,14 @@ find-cache-dir@^1.0.0:
|
||||
make-dir "^1.0.0"
|
||||
pkg-dir "^2.0.0"
|
||||
|
||||
find-cache-dir@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
|
||||
dependencies:
|
||||
commondir "^1.0.1"
|
||||
make-dir "^1.0.0"
|
||||
pkg-dir "^3.0.0"
|
||||
|
||||
find-index@^0.1.1:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4"
|
||||
@@ -5569,6 +5611,12 @@ find-up@^2.0.0, find-up@^2.1.0:
|
||||
dependencies:
|
||||
locate-path "^2.0.0"
|
||||
|
||||
find-up@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
|
||||
dependencies:
|
||||
locate-path "^3.0.0"
|
||||
|
||||
findup-sync@^0.4.2:
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12"
|
||||
@@ -6513,9 +6561,9 @@ hoek@4.x.x:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb"
|
||||
|
||||
home-assistant-js-websocket@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.0.0.tgz#498828a29827bdd1f3e99cf3b5e152694cededbf"
|
||||
home-assistant-js-websocket@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-3.1.2.tgz#c82e79eb644eac0206cd9c07d10c83c7d052c2b7"
|
||||
|
||||
home-or-tmp@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -7540,6 +7588,13 @@ locate-path@^2.0.0:
|
||||
p-locate "^2.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
locate-path@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
|
||||
dependencies:
|
||||
p-locate "^3.0.0"
|
||||
path-exists "^3.0.0"
|
||||
|
||||
lodash._baseassign@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e"
|
||||
@@ -7972,7 +8027,7 @@ lru-cache@2:
|
||||
version "2.7.3"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
|
||||
|
||||
lru-cache@^4.0.1, lru-cache@^4.0.2, lru-cache@^4.1.1:
|
||||
lru-cache@^4.0.1, lru-cache@^4.0.2, lru-cache@^4.1.1, lru-cache@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
|
||||
dependencies:
|
||||
@@ -8292,6 +8347,21 @@ mississippi@^2.0.0:
|
||||
stream-each "^1.1.0"
|
||||
through2 "^2.0.0"
|
||||
|
||||
mississippi@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
|
||||
dependencies:
|
||||
concat-stream "^1.5.0"
|
||||
duplexify "^3.4.2"
|
||||
end-of-stream "^1.1.0"
|
||||
flush-write-stream "^1.0.0"
|
||||
from2 "^2.1.0"
|
||||
parallel-transform "^1.1.0"
|
||||
pump "^3.0.0"
|
||||
pumpify "^1.3.3"
|
||||
stream-each "^1.1.0"
|
||||
through2 "^2.0.0"
|
||||
|
||||
mixin-deep@^1.2.0:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
|
||||
@@ -8878,12 +8948,24 @@ p-limit@^1.0.0, p-limit@^1.1.0:
|
||||
dependencies:
|
||||
p-try "^1.0.0"
|
||||
|
||||
p-limit@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.0.0.tgz#e624ed54ee8c460a778b3c9f3670496ff8a57aec"
|
||||
dependencies:
|
||||
p-try "^2.0.0"
|
||||
|
||||
p-locate@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43"
|
||||
dependencies:
|
||||
p-limit "^1.1.0"
|
||||
|
||||
p-locate@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
|
||||
dependencies:
|
||||
p-limit "^2.0.0"
|
||||
|
||||
p-map@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a"
|
||||
@@ -8898,6 +8980,10 @@ p-try@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
|
||||
|
||||
p-try@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1"
|
||||
|
||||
package-json@^2.0.0:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb"
|
||||
@@ -9157,6 +9243,12 @@ pkg-dir@^2.0.0:
|
||||
dependencies:
|
||||
find-up "^2.1.0"
|
||||
|
||||
pkg-dir@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3"
|
||||
dependencies:
|
||||
find-up "^3.0.0"
|
||||
|
||||
plist@^2.0.1:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/plist/-/plist-2.1.0.tgz#57ccdb7a0821df21831217a3cad54e3e146a1025"
|
||||
@@ -9700,6 +9792,13 @@ pump@^2.0.0, pump@^2.0.1:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
pumpify@^1.3.3:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
|
||||
@@ -10397,6 +10496,14 @@ schema-utils@^0.4.4, schema-utils@^0.4.5:
|
||||
ajv "^6.1.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
schema-utils@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
||||
dependencies:
|
||||
ajv "^6.1.0"
|
||||
ajv-errors "^1.0.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
scoped-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-1.0.0.tgz#a346bb1acd4207ae70bd7c0c7ca9e566b6baddb8"
|
||||
@@ -10925,6 +11032,12 @@ ssri@^5.2.4:
|
||||
dependencies:
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
ssri@^6.0.0:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8"
|
||||
dependencies:
|
||||
figgy-pudding "^3.5.1"
|
||||
|
||||
stable@^0.1.6:
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
|
||||
|
Reference in New Issue
Block a user