Compare commits

...

23 Commits

Author SHA1 Message Date
Paulus Schoutsen
e3a137c675 Version bump to 20180911.0 2018-09-11 21:35:28 +02:00
Paulus Schoutsen
10aa99abdc Update translations 2018-09-11 21:35:08 +02:00
Paulus Schoutsen
34567d451f Add UI for tokens (#1656)
* Add UI for tokens

* Update strings

* Update text

* Update text
2018-09-11 21:29:40 +02:00
PhracturedBlue
494e3dc62c Fix authorization and display issues in mailbox view (#1610)
* Mailbox: Fix authorization issues.  Remove backdrop

* Fix linting issues

* Use HA Dialog system.  Fix authorization

* Add back missing backdrop

* Linting errors

* Use callApi.  Add error checking and spinner

* linting error

* more linting errors

* minor requested fixes

* Use let/const.  Fix lint issues

* Remove blob test that can never fail

* More let vs var fixes

* More minor requested fixes

* Rework code to use fetchWithAuth

* Async tweaks

* Lint

* Fix onboarding

* Add credentials for onboarding

* Lint
2018-09-11 11:33:57 +02:00
Paulus Schoutsen
0997274f29 Update external auth (#1655)
* Update external auth

* Lint
2018-09-11 10:24:01 +02:00
cdce8p
76161329b6 Add lovelace template extension points (#1653) 2018-09-10 23:15:29 +02:00
John Arild Berentsen
8505750958 Load ozw-log in new window, and add tail-like button. (#1652)
* Load log in new window, and add tail

* avoid duplicate code
2018-09-10 21:53:16 +02:00
Paulus Schoutsen
4077105db1 Version bump to 20180910.0 2018-09-10 13:28:51 +02:00
Paulus Schoutsen
3f31d83a55 Update translations 2018-09-10 13:28:33 +02:00
Paulus Schoutsen
d729e3c567 Update HAWS to 3.1.2 2018-09-10 13:25:50 +02:00
Paulus Schoutsen
9af75f9a43 Prevent changing domain entity ID (#1650) 2018-09-10 13:14:21 +02:00
Alessandro Staniscia
d32d334a2e Review Docker management (#1113)
* Review Docker management ( linked with #934 )

*  fix comment by @balloob

* Explicit remove of  package-lock.json

* moved on feature branch, merge docker scripts, added documetation

* Used alphine as requested by @balloob on https://github.com/home-assistant/home-assistant-polymer/pull/947 and followed the @mcspr comment https://github.com/home-assistant/home-assistant-polymer/issues/934

* Remove package-lock from gitignore, we don't use npm

* Update for new build instructions
2018-09-10 11:58:18 +02:00
Charles Garwood
94006a843c ZWave Panel Updates (#1647)
* Add padding to zwave log text

* Replace entity dropdown with actual entity_id

* Add Node Information button for more-info popup, and remove less-functional Node Info card.

* Fix indentation

* Address review comments

* Fix lint/mixin

* Update comment
2018-09-10 10:16:10 +02:00
Jason Hu
4790590327 Try to resolve workbox warning (#1648) 2018-09-10 10:15:20 +02:00
Charles Garwood
7cf7763e21 Fix body stream already read error (#1646) 2018-09-08 21:06:07 +02:00
Paulus Schoutsen
0d7979a72f Add revoke token to (external) auth (#1639)
* Add revoke token to external auth

* Lint

* Update to HAWS 3.1.1

* Fix constant
2018-09-07 20:37:06 +02:00
Paulus Schoutsen
300425e698 Redirect to onboarding from auth page (#1640)
* Redirect to onboarding from auth page

* Remove left over trial code
2018-09-07 20:13:00 +02:00
Stephen Vanterpool
59010baf89 Fix the way calls are made over the javascript bridge (#1644)
* Fix the way calls are made over the javascript bridge

* Update external_auth.js
2018-09-07 20:12:52 +02:00
Paulus Schoutsen
47fcb122a2 Don't delete system generated user (#1638) 2018-09-07 19:41:06 +02:00
Paulus Schoutsen
bbb50b1397 Better handle auth (#1637)
* Better handle auth

* Lint
2018-09-07 19:40:56 +02:00
Paulus Schoutsen
ae8724d699 Compress using zopfli (#1636) 2018-09-05 11:41:03 +02:00
Paulus Schoutsen
2169f6979d Remove link to alexa web 2018-09-04 15:02:35 +02:00
Paulus Schoutsen
9cc577e9c7 Add external auth (#1621)
* Add external auth

* Lint

* Warn when external auth not present
2018-09-03 09:00:39 -07:00
68 changed files with 1881 additions and 681 deletions

View File

@@ -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" ]

View File

@@ -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.

View File

@@ -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",

View 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
View 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

View File

@@ -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',

View File

@@ -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;

View 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;
}
}

View File

@@ -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({

View File

@@ -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);

View File

@@ -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');
}
}
};

View File

@@ -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

View File

@@ -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,
}
};
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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">

View File

@@ -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>

View File

@@ -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>

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>
`;
}

View File

@@ -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>
`;
}

View File

@@ -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>
`;
}

View File

@@ -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,

View File

@@ -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>
`;
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>
`;
}

View File

@@ -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>
`;
}

View File

@@ -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>
`;
}

View File

@@ -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>
`;
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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>
`;
}

View File

@@ -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>
`;
}

View 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);

View File

@@ -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;
});
}

View 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);

View File

@@ -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');
}

View 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);

View File

@@ -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": {

View 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);
};

View File

@@ -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;
}

View File

@@ -562,7 +562,8 @@
}
},
"error": {
"invalid_auth": "Невалидно потребителско име или парола"
"invalid_auth": "Невалидно потребителско име или парола",
"invalid_code": "Невалиден код за аутентикация"
}
},
"trusted_networks": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": {

View File

@@ -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": "Соединение потеряно. Переподключение ..."

View File

@@ -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": {

View File

@@ -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"
}
},

View File

@@ -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",

View File

@@ -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": {

View File

@@ -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
View File

@@ -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"