mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-10 10:56:34 +00:00
Authorize onboarding (#1603)
* Tweak onboarding + authorize * Authorize/Onboarding pimp * More tweaks * Comments
This commit is contained in:
parent
772208ba22
commit
f443942e03
@ -22,7 +22,10 @@ const TRANSLATION_FRAGMENTS = [
|
|||||||
'history',
|
'history',
|
||||||
'logbook',
|
'logbook',
|
||||||
'mailbox',
|
'mailbox',
|
||||||
|
'profile',
|
||||||
'shopping-list',
|
'shopping-list',
|
||||||
|
'page-authorize',
|
||||||
|
'page-onboarding',
|
||||||
];
|
];
|
||||||
|
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
|
@ -117,6 +117,7 @@
|
|||||||
"polymer-analyzer": "^3.0.1",
|
"polymer-analyzer": "^3.0.1",
|
||||||
"polymer-bundler": "^4.0.1",
|
"polymer-bundler": "^4.0.1",
|
||||||
"polymer-cli": "^1.7.4",
|
"polymer-cli": "^1.7.4",
|
||||||
|
"raw-loader": "^0.5.1",
|
||||||
"reify": "^0.16.2",
|
"reify": "^0.16.2",
|
||||||
"require-dir": "^1.0.0",
|
"require-dir": "^1.0.0",
|
||||||
"sinon": "^6.0.0",
|
"sinon": "^6.0.0",
|
||||||
|
@ -2,46 +2,64 @@ import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
|||||||
import '@polymer/paper-button/paper-button.js';
|
import '@polymer/paper-button/paper-button.js';
|
||||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||||
import '../components/ha-form.js';
|
import '../components/ha-form.js';
|
||||||
import EventsMixin from '../mixins/events-mixin.js';
|
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
|
||||||
|
|
||||||
/*
|
class HaAuthFlow extends LocalizeLiteMixin(PolymerElement) {
|
||||||
* @appliesMixin EventsMixin
|
|
||||||
*/
|
|
||||||
class HaAuthFlow extends EventsMixin(PolymerElement) {
|
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
|
:host {
|
||||||
|
/* So we can set min-height to avoid jumping during loading */
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
.action {
|
.action {
|
||||||
margin: 32px 0;
|
margin: 24px 0 8px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<template is="dom-if" if="[[_equals(_state, "loading")]]">
|
<form>
|
||||||
Please wait
|
<template is="dom-if" if="[[_equals(_state, "loading")]]">
|
||||||
</template>
|
[[localize('ui.panel.page-authorize.form.working')]]:
|
||||||
<template is="dom-if" if="[[_equals(_state, "error")]]">
|
|
||||||
Something went wrong
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_equals(_state, "step")]]">
|
|
||||||
<template is="dom-if" if="[[_equals(_step.type, "abort")]]">
|
|
||||||
Aborted
|
|
||||||
</template>
|
</template>
|
||||||
<template is="dom-if" if="[[_equals(_step.type, "create_entry")]]">
|
<template is="dom-if" if="[[_equals(_state, "error")]]">
|
||||||
Success!
|
[[localize('ui.panel.page-authorize.form.unknown_error')]]:
|
||||||
</template>
|
</template>
|
||||||
<template is="dom-if" if="[[_equals(_step.type, "form")]]">
|
<template is="dom-if" if="[[_equals(_state, "step")]]">
|
||||||
<ha-form data="{{_stepData}}" schema="[[_step.data_schema]]" error="[[_step.errors]]"></ha-form>
|
<template is="dom-if" if="[[_equals(_step.type, "abort")]]">
|
||||||
|
[[localize('ui.panel.page-authorize.abort_intro')]]:
|
||||||
|
<ha-markdown content="[[_computeStepAbortedReason(localize, _step)]]"></ha-markdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template is="dom-if" if="[[_equals(_step.type, "form")]]">
|
||||||
|
<template is="dom-if" if="[[_computeStepDescription(localize, _step)]]">
|
||||||
|
<ha-markdown content="[[_computeStepDescription(localize, _step)]]" allow-svg></ha-markdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<ha-form
|
||||||
|
data="{{_stepData}}"
|
||||||
|
schema="[[_step.data_schema]]"
|
||||||
|
error="[[_step.errors]]"
|
||||||
|
compute-label="[[_computeLabelCallback(localize, _step)]]"
|
||||||
|
compute-error="[[_computeErrorCallback(localize, _step)]]"
|
||||||
|
></ha-form>
|
||||||
|
</template>
|
||||||
|
<div class='action'>
|
||||||
|
<paper-button
|
||||||
|
raised
|
||||||
|
on-click='_handleSubmit'
|
||||||
|
>[[_computeSubmitCaption(_step.type)]]</paper-button>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class='action'>
|
</form>
|
||||||
<paper-button raised on-click="_handleSubmit">[[_computeSubmitCaption(_step.type)]]</paper-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
authProvider: Object,
|
authProvider: {
|
||||||
|
type: Object,
|
||||||
|
observer: '_providerChanged',
|
||||||
|
},
|
||||||
clientId: String,
|
clientId: String,
|
||||||
redirectUri: String,
|
redirectUri: String,
|
||||||
oauth2State: String,
|
oauth2State: String,
|
||||||
@ -53,11 +71,14 @@ class HaAuthFlow extends EventsMixin(PolymerElement) {
|
|||||||
type: Object,
|
type: Object,
|
||||||
value: () => ({}),
|
value: () => ({}),
|
||||||
},
|
},
|
||||||
_step: Object,
|
_step: {
|
||||||
|
type: Object,
|
||||||
|
notify: true,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async ready() {
|
ready() {
|
||||||
super.ready();
|
super.ready();
|
||||||
|
|
||||||
this.addEventListener('keypress', (ev) => {
|
this.addEventListener('keypress', (ev) => {
|
||||||
@ -67,28 +88,45 @@ class HaAuthFlow extends EventsMixin(PolymerElement) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
async _providerChanged(newProvider, oldProvider) {
|
||||||
super.connectedCallback();
|
if (oldProvider && this._step && this._step.type === 'form') {
|
||||||
|
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
fetch('/auth/login_flow', {
|
try {
|
||||||
method: 'POST',
|
const response = await fetch('/auth/login_flow', {
|
||||||
credentials: 'same-origin',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
credentials: 'same-origin',
|
||||||
client_id: this.clientId,
|
body: JSON.stringify({
|
||||||
handler: [this.authProvider.type, this.authProvider.id],
|
client_id: this.clientId,
|
||||||
redirect_uri: this.redirectUri,
|
handler: [newProvider.type, newProvider.id],
|
||||||
})
|
redirect_uri: this.redirectUri,
|
||||||
}).then((response) => {
|
})
|
||||||
if (!response.ok) throw new Error();
|
});
|
||||||
return response.json();
|
|
||||||
}).then(step => this.setProperties({
|
const step = await response.json();
|
||||||
_step: step,
|
this._updateStep(step);
|
||||||
_state: 'step',
|
} catch (err) {
|
||||||
})).catch((err) => {
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error('Error starting auth flow', err);
|
console.error('Error starting auth flow', err);
|
||||||
this._state = 'error';
|
this._state = 'error';
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateStep(step) {
|
||||||
|
const props = {
|
||||||
|
_step: step,
|
||||||
|
_state: 'step',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this._step && step.step_id !== this._step.step_id) {
|
||||||
|
props._stepData = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setProperties(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
_equals(a, b) {
|
_equals(a, b) {
|
||||||
@ -99,25 +137,52 @@ class HaAuthFlow extends EventsMixin(PolymerElement) {
|
|||||||
return stepType === 'form' ? 'Next' : 'Start over';
|
return stepType === 'form' ? 'Next' : 'Start over';
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleSubmit() {
|
_computeStepAbortedReason(localize, step) {
|
||||||
|
return localize(`ui.panel.page-authorize.form.providers.${step.handler[0]}.abort.${step.reason}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeStepDescription(localize, step) {
|
||||||
|
const args = [`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.description`];
|
||||||
|
const placeholders = step.description_placeholders || {};
|
||||||
|
Object.keys(placeholders).forEach((key) => {
|
||||||
|
args.push(key);
|
||||||
|
args.push(placeholders[key]);
|
||||||
|
});
|
||||||
|
return localize(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeLabelCallback(localize, step) {
|
||||||
|
// Returns a callback for ha-form to calculate labels per schema object
|
||||||
|
return schema => localize(`ui.panel.page-authorize.form.providers.${step.handler[0]}.step.${step.step_id}.data.${schema.name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeErrorCallback(localize, step) {
|
||||||
|
// Returns a callback for ha-form to calculate error messages
|
||||||
|
return error => localize(`ui.panel.page-authorize.form.providers.${step.handler[0]}.error.${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleSubmit() {
|
||||||
if (this._step.type !== 'form') {
|
if (this._step.type !== 'form') {
|
||||||
this.fire('reset');
|
this._providerChanged(this.authProvider, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._state = 'loading';
|
this._state = 'loading';
|
||||||
|
// To avoid a jumping UI.
|
||||||
|
this.style.setProperty('min-height', `${this.offsetHeight}px`);
|
||||||
|
|
||||||
const postData = Object.assign({}, this._stepData, {
|
const postData = Object.assign({}, this._stepData, {
|
||||||
client_id: this.clientId,
|
client_id: this.clientId,
|
||||||
});
|
});
|
||||||
|
|
||||||
fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
try {
|
||||||
method: 'POST',
|
const response = await fetch(`/auth/login_flow/${this._step.flow_id}`, {
|
||||||
credentials: 'same-origin',
|
method: 'POST',
|
||||||
body: JSON.stringify(postData)
|
credentials: 'same-origin',
|
||||||
}).then((response) => {
|
body: JSON.stringify(postData)
|
||||||
if (!response.ok) throw new Error();
|
});
|
||||||
return response.json();
|
|
||||||
}).then((newStep) => {
|
const newStep = await response.json();
|
||||||
|
|
||||||
if (newStep.type === 'create_entry') {
|
if (newStep.type === 'create_entry') {
|
||||||
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
|
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
|
||||||
let url = this.redirectUri;
|
let url = this.redirectUri;
|
||||||
@ -136,20 +201,14 @@ class HaAuthFlow extends EventsMixin(PolymerElement) {
|
|||||||
document.location = url;
|
document.location = url;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this._updateStep(newStep);
|
||||||
const props = {
|
} catch (err) {
|
||||||
_step: newStep,
|
|
||||||
_state: 'step',
|
|
||||||
};
|
|
||||||
if (newStep.step_id !== this._step.step_id) {
|
|
||||||
props._stepData = {};
|
|
||||||
}
|
|
||||||
this.setProperties(props);
|
|
||||||
}).catch((err) => {
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
console.error('Error loading auth providers', err);
|
console.error('Error submitting step', err);
|
||||||
this._state = 'error-loading';
|
this._state = 'error-loading';
|
||||||
});
|
} finally {
|
||||||
|
this.style.setProperty('min-height', '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define('ha-auth-flow', HaAuthFlow);
|
customElements.define('ha-auth-flow', HaAuthFlow);
|
||||||
|
@ -1,76 +1,73 @@
|
|||||||
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
|
|
||||||
import '@polymer/polymer/lib/elements/dom-if.js';
|
import '@polymer/polymer/lib/elements/dom-if.js';
|
||||||
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
||||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
|
||||||
import '../auth/ha-auth-flow.js';
|
import '../components/ha-markdown.js';
|
||||||
import '../auth/ha-pick-auth-provider.js';
|
|
||||||
|
|
||||||
class HaAuthorize extends PolymerElement {
|
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
|
||||||
|
|
||||||
|
import '../auth/ha-auth-flow.js';
|
||||||
|
|
||||||
|
class HaAuthorize extends LocalizeLiteMixin(PolymerElement) {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="iron-flex iron-positioning"></style>
|
|
||||||
<style>
|
<style>
|
||||||
.content {
|
ha-markdown a {
|
||||||
padding: 20px 16px;
|
color: var(--primary-color);
|
||||||
max-width: 360px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
|
ha-markdown p:last-child{
|
||||||
.header {
|
margin-bottom: 0;
|
||||||
text-align: center;
|
|
||||||
font-size: 1.96em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
}
|
||||||
|
ha-pick-auth-provider {
|
||||||
.header img {
|
display: block;
|
||||||
margin-right: 16px;
|
margin-top: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<div class="content layout vertical fit">
|
|
||||||
<div class='header'>
|
|
||||||
<img src="/static/icons/favicon-192x192.png" height="52">
|
|
||||||
Home Assistant
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>Logging in to <b>[[clientId]]</b>.</p>
|
<template is="dom-if" if="[[!_authProviders]]">
|
||||||
|
<p>[[localize('ui.panel.page-authorize.initializing')]]</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template is="dom-if" if="[[_authProvider]]">
|
<template is="dom-if" if="[[_authProviders]]">
|
||||||
<ha-auth-flow
|
<ha-markdown content='[[_computeIntro(localize, clientId, _authProvider)]]'></ha-markdown>
|
||||||
client-id="[[clientId]]"
|
|
||||||
redirect-uri="[[redirectUri]]"
|
<ha-auth-flow
|
||||||
oauth2-state="[[oauth2State]]"
|
resources="[[resources]]"
|
||||||
auth-provider="[[_authProvider]]"
|
client-id="[[clientId]]"
|
||||||
on-reset="_handleReset"
|
redirect-uri="[[redirectUri]]"
|
||||||
></ha-auth-flow>
|
oauth2-state="[[oauth2State]]"
|
||||||
</template>
|
auth-provider="[[_authProvider]]"
|
||||||
<template is="dom-if" if="[[!_authProvider]]">
|
step="{{step}}"
|
||||||
|
></ha-auth-flow>
|
||||||
|
|
||||||
|
<template is="dom-if" if="[[_computeMultiple(_authProviders)]]">
|
||||||
<ha-pick-auth-provider
|
<ha-pick-auth-provider
|
||||||
|
resources="[[resources]]"
|
||||||
client-id="[[clientId]]"
|
client-id="[[clientId]]"
|
||||||
|
auth-providers="[[_computeInactiveProvders(_authProvider, _authProviders)]]"
|
||||||
on-pick="_handleAuthProviderPick"
|
on-pick="_handleAuthProviderPick"
|
||||||
></ha-pick-auth-provider>
|
></ha-pick-auth-provider>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</template>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
_authProvider: {
|
_authProvider: String,
|
||||||
type: String,
|
_authProviders: Array,
|
||||||
value: null,
|
|
||||||
},
|
|
||||||
clientId: String,
|
clientId: String,
|
||||||
redirectUri: String,
|
redirectUri: String,
|
||||||
oauth2State: String,
|
oauth2State: String,
|
||||||
|
translationFragment: {
|
||||||
|
type: String,
|
||||||
|
value: 'page-authorize',
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ready() {
|
|
||||||
|
async ready() {
|
||||||
super.ready();
|
super.ready();
|
||||||
const query = {};
|
const query = {};
|
||||||
const values = location.search.substr(1).split('&');
|
const values = location.search.substr(1).split('&');
|
||||||
@ -85,12 +82,49 @@ class HaAuthorize extends PolymerElement {
|
|||||||
if (query.redirect_uri) props.redirectUri = query.redirect_uri;
|
if (query.redirect_uri) props.redirectUri = query.redirect_uri;
|
||||||
if (query.state) props.oauth2State = query.state;
|
if (query.state) props.oauth2State = query.state;
|
||||||
this.setProperties(props);
|
this.setProperties(props);
|
||||||
|
|
||||||
|
import(/* webpackChunkName: "pick-auth-provider" */ '../auth/ha-pick-auth-provider.js');
|
||||||
|
|
||||||
|
// Fetch auth providers
|
||||||
|
try {
|
||||||
|
const response = await window.providersPromise;
|
||||||
|
const authProviders = await response.json();
|
||||||
|
|
||||||
|
if (authProviders.length === 0) {
|
||||||
|
alert('No auth providers returned. Unable to finish login.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setProperties({
|
||||||
|
_authProviders: authProviders,
|
||||||
|
_authProvider: authProviders[0],
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
console.error('Error loading auth providers', err);
|
||||||
|
this._state = 'error-loading';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_handleAuthProviderPick(ev) {
|
|
||||||
|
_computeMultiple(array) {
|
||||||
|
return array && array.length > 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleAuthProviderPick(ev) {
|
||||||
this._authProvider = ev.detail;
|
this._authProvider = ev.detail;
|
||||||
}
|
}
|
||||||
_handleReset() {
|
|
||||||
this._authProvider = null;
|
_computeInactiveProvders(curProvider, providers) {
|
||||||
|
return providers.filter(prv =>
|
||||||
|
prv.type !== curProvider.type || prv.id !== curProvider.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
_computeIntro(localize, clientId, authProvider) {
|
||||||
|
return (
|
||||||
|
localize('ui.panel.page-authorize.authorizing_client', 'clientId', clientId) +
|
||||||
|
'\n\n' +
|
||||||
|
localize('ui.panel.page-authorize.logging_in_with', 'authProviderName', authProvider.name)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
customElements.define('ha-authorize', HaAuthorize);
|
customElements.define('ha-authorize', HaAuthorize);
|
||||||
|
@ -4,11 +4,12 @@ import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
|||||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
|
|
||||||
import EventsMixin from '../mixins/events-mixin.js';
|
import EventsMixin from '../mixins/events-mixin.js';
|
||||||
|
import LocalizeLiteMixin from '../mixins/localize-lite-mixin.js';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @appliesMixin EventsMixin
|
* @appliesMixin EventsMixin
|
||||||
*/
|
*/
|
||||||
class HaPickAuthProvider extends EventsMixin(PolymerElement) {
|
class HaPickAuthProvider extends EventsMixin(LocalizeLiteMixin(PolymerElement)) {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
@ -19,23 +20,12 @@ class HaPickAuthProvider extends EventsMixin(PolymerElement) {
|
|||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<template is="dom-if" if="[[_equal(_state, "loading")]]">
|
<p>[[localize('ui.panel.page-authorize.pick_auth_provider')]]:</p>
|
||||||
Loading auth providers.
|
<template is="dom-repeat" items="[[authProviders]]">
|
||||||
</template>
|
<paper-item on-click="_handlePick">
|
||||||
<template is="dom-if" if="[[_equal(_state, "no-results")]]">
|
<paper-item-body>[[item.name]]</paper-item-body>
|
||||||
No auth providers found.
|
<iron-icon icon="hass:chevron-right"></iron-icon>
|
||||||
</template>
|
</paper-item>
|
||||||
<template is="dom-if" if="[[_equal(_state, "error-loading")]]">
|
|
||||||
Error loading
|
|
||||||
</template>
|
|
||||||
<template is="dom-if" if="[[_equal(_state, "pick")]]">
|
|
||||||
<p>Pick an auth provider to log in with:</p>
|
|
||||||
<template is="dom-repeat" items="[[authProviders]]">
|
|
||||||
<paper-item on-click="_handlePick">
|
|
||||||
<paper-item-body>[[item.name]]</paper-item-body>
|
|
||||||
<iron-icon icon="hass:chevron-right"></iron-icon>
|
|
||||||
</paper-item>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -47,29 +37,8 @@ class HaPickAuthProvider extends EventsMixin(PolymerElement) {
|
|||||||
value: 'loading'
|
value: 'loading'
|
||||||
},
|
},
|
||||||
authProviders: Array,
|
authProviders: Array,
|
||||||
clientId: String,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
connectedCallback() {
|
|
||||||
super.connectedCallback();
|
|
||||||
|
|
||||||
fetch('/auth/providers', { credentials: 'same-origin' }).then((response) => {
|
|
||||||
if (!response.ok) throw new Error();
|
|
||||||
return response.json();
|
|
||||||
}).then((authProviders) => {
|
|
||||||
this.setProperties({
|
|
||||||
authProviders,
|
|
||||||
_state: 'pick',
|
|
||||||
});
|
|
||||||
if (authProviders.length === 1) {
|
|
||||||
this.fire('pick', authProviders[0]);
|
|
||||||
}
|
|
||||||
}).catch((err) => {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
console.error('Error loading auth providers', err);
|
|
||||||
this._state = 'error-loading';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_handlePick(ev) {
|
_handlePick(ev) {
|
||||||
this.fire('pick', ev.model.item);
|
this.fire('pick', ev.model.item);
|
||||||
|
@ -23,7 +23,7 @@ class HaForm extends EventsMixin(PolymerElement) {
|
|||||||
</style>
|
</style>
|
||||||
<template is="dom-if" if="[[_isArray(schema)]]" restamp="">
|
<template is="dom-if" if="[[_isArray(schema)]]" restamp="">
|
||||||
<template is="dom-if" if="[[error.base]]">
|
<template is="dom-if" if="[[error.base]]">
|
||||||
[[computeError(error.base, schema)]]
|
<div class='error'>[[computeError(error.base, schema)]]</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template is="dom-repeat" items="[[schema]]">
|
<template is="dom-repeat" items="[[schema]]">
|
||||||
|
@ -17,7 +17,11 @@ import '../components/ha-iconset-svg.js';
|
|||||||
import '../layouts/app/home-assistant.js';
|
import '../layouts/app/home-assistant.js';
|
||||||
|
|
||||||
/* polyfill for paper-dropdown */
|
/* polyfill for paper-dropdown */
|
||||||
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js');
|
setTimeout(
|
||||||
|
() =>
|
||||||
|
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js'),
|
||||||
|
2000
|
||||||
|
);
|
||||||
|
|
||||||
setPassiveTouchGestures(true);
|
setPassiveTouchGestures(true);
|
||||||
/* LastPass createElement workaround. See #428 */
|
/* LastPass createElement workaround. See #428 */
|
||||||
|
@ -8,4 +8,5 @@ import '../resources/roboto.js';
|
|||||||
import '../auth/ha-authorize.js';
|
import '../auth/ha-authorize.js';
|
||||||
|
|
||||||
/* polyfill for paper-dropdown */
|
/* polyfill for paper-dropdown */
|
||||||
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js');
|
setTimeout(() =>
|
||||||
|
import(/* webpackChunkName: "polyfill-web-animations-next" */ 'web-animations-js/web-animations-next-lite.min.js'), 2000);
|
||||||
|
15
src/html/_header.html.template
Normal file
15
src/html/_header.html.template
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel='manifest' href='/manifest.json' crossorigin="use-credentials">
|
||||||
|
<link rel='icon' href='/static/icons/favicon.ico'>
|
||||||
|
<meta name='viewport' content='width=device-width, user-scalable=no'>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-weight: 400;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,30 +1,45 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name='viewport' content='width=device-width, user-scalable=no'>
|
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
|
|
||||||
<title>Home Assistant</title>
|
<title>Home Assistant</title>
|
||||||
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
|
||||||
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
||||||
|
<%= require('raw-loader!./_header.html.template') %>
|
||||||
<style>
|
<style>
|
||||||
body {
|
.content {
|
||||||
font-family: Roboto, sans-serif;
|
padding: 20px 16px;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
max-width: 360px;
|
||||||
-webkit-font-smoothing: antialiased;
|
margin: 0 auto;
|
||||||
font-weight: 400;
|
}
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
.header {
|
||||||
height: 100vh;
|
font-size: 1.96em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header img {
|
||||||
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<<%= tag %>>Loading</<%= tag %>>
|
<div class="content">
|
||||||
|
<div class='header'>
|
||||||
|
<img src="/static/icons/favicon-192x192.png" height="52">
|
||||||
|
Home Assistant
|
||||||
|
</div>
|
||||||
|
<ha-authorize><p>Initializing</p></ha-authorize>
|
||||||
|
</div>
|
||||||
<% if (!latestBuild) { %>
|
<% if (!latestBuild) { %>
|
||||||
<script src="/static/custom-elements-es5-adapter.js"></script>
|
<script src="/static/custom-elements-es5-adapter.js"></script>
|
||||||
<script src="<%= compatibility %>"></script>
|
<script src="<%= compatibility %>"></script>
|
||||||
<% } %>
|
<% } %>
|
||||||
<script>
|
<script>
|
||||||
|
window.providersPromise = fetch('/auth/providers', { credentials: 'same-origin' });
|
||||||
|
|
||||||
var webComponentsSupported = (
|
var webComponentsSupported = (
|
||||||
'customElements' in window &&
|
'customElements' in window &&
|
||||||
'content' in document.createElement('template'));
|
'content' in document.createElement('template'));
|
@ -1,17 +1,14 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Home Assistant</title>
|
|
||||||
|
|
||||||
<link rel='manifest' href='/manifest.json' crossorigin="use-credentials">
|
|
||||||
<link rel='icon' href='/static/icons/favicon.ico'>
|
|
||||||
<link rel='apple-touch-icon' sizes='180x180'
|
|
||||||
href='/static/icons/favicon-apple-180x180.png'>
|
|
||||||
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#3fbbf4">
|
|
||||||
<link rel='preload' href='<%= coreJS %>' as='script'/>
|
<link rel='preload' href='<%= coreJS %>' as='script'/>
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
||||||
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Medium.ttf' as='font' crossorigin />
|
||||||
|
<%= require('raw-loader!./_header.html.template') %>
|
||||||
|
<title>Home Assistant</title>
|
||||||
|
<link rel='apple-touch-icon' sizes='180x180'
|
||||||
|
href='/static/icons/favicon-apple-180x180.png'>
|
||||||
|
<link rel="mask-icon" href="/static/icons/mask-icon.svg" color="#3fbbf4">
|
||||||
<meta name='apple-mobile-web-app-capable' content='yes'>
|
<meta name='apple-mobile-web-app-capable' content='yes'>
|
||||||
<meta name="msapplication-square70x70logo" content="/static/icons/tile-win-70x70.png"/>
|
<meta name="msapplication-square70x70logo" content="/static/icons/tile-win-70x70.png"/>
|
||||||
<meta name="msapplication-square150x150logo" content="/static/icons/tile-win-150x150.png"/>
|
<meta name="msapplication-square150x150logo" content="/static/icons/tile-win-150x150.png"/>
|
||||||
@ -20,19 +17,8 @@
|
|||||||
<meta name="msapplication-TileColor" content="#3fbbf4ff"/>
|
<meta name="msapplication-TileColor" content="#3fbbf4ff"/>
|
||||||
<meta name='mobile-web-app-capable' content='yes'>
|
<meta name='mobile-web-app-capable' content='yes'>
|
||||||
<meta name='referrer' content='same-origin'>
|
<meta name='referrer' content='same-origin'>
|
||||||
<meta name='viewport' content='width=device-width, user-scalable=no'>
|
|
||||||
<meta name='theme-color' content='{{ theme_color }}'>
|
<meta name='theme-color' content='{{ theme_color }}'>
|
||||||
<style>
|
<style>
|
||||||
body {
|
|
||||||
font-family: Roboto, sans-serif;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
font-weight: 400;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ha-init-skeleton::before {
|
#ha-init-skeleton::before {
|
||||||
display: block;
|
display: block;
|
||||||
content: "";
|
content: "";
|
||||||
|
57
src/html/onboarding.html.template
Normal file
57
src/html/onboarding.html.template
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Home Assistant</title>
|
||||||
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Light.ttf' as='font' crossorigin />
|
||||||
|
<link rel='preload' href='/static/fonts/roboto/Roboto-Regular.ttf' as='font' crossorigin />
|
||||||
|
<%= require('raw-loader!./_header.html.template') %>
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
padding: 20px 16px;
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.96em;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header img {
|
||||||
|
margin-right: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<div class='header'>
|
||||||
|
<img src="/static/icons/favicon-192x192.png" height="52">
|
||||||
|
Home Assistant
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ha-onboarding>Initializing…</ha-onboarding>
|
||||||
|
</div>
|
||||||
|
<% if (!latestBuild) { %>
|
||||||
|
<script src="/static/custom-elements-es5-adapter.js"></script>
|
||||||
|
<script src="<%= compatibility %>"></script>
|
||||||
|
<% } %>
|
||||||
|
<script>
|
||||||
|
window.stepsPromise = fetch('/api/onboarding', { credentials: 'same-origin' });
|
||||||
|
|
||||||
|
var webComponentsSupported = (
|
||||||
|
'customElements' in window &&
|
||||||
|
'content' in document.createElement('template'));
|
||||||
|
if (!webComponentsSupported) {
|
||||||
|
var e = document.createElement('script');
|
||||||
|
e.src = '/static/webcomponents-bundle.js';
|
||||||
|
document.write(e.outerHTML);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script src="<%= entrypoint %>"></script>
|
||||||
|
<script src='<%= hassIconsJS %>' async></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
52
src/mixins/localize-lite-mixin.js
Normal file
52
src/mixins/localize-lite-mixin.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Lite mixin to add localization without depending on the Hass object.
|
||||||
|
*/
|
||||||
|
import { mixinBehaviors } from '@polymer/polymer/lib/legacy/class.js';
|
||||||
|
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
||||||
|
import { AppLocalizeBehavior } from '../util/app-localize-behavior.js';
|
||||||
|
import { getActiveTranslation, getTranslation } from '../util/hass-translation.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @polymerMixin
|
||||||
|
* @appliesMixin AppLocalizeBehavior
|
||||||
|
*/
|
||||||
|
export default dedupingMixin(superClass =>
|
||||||
|
class extends mixinBehaviors([AppLocalizeBehavior], superClass) {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
language: {
|
||||||
|
type: String,
|
||||||
|
value: getActiveTranslation(),
|
||||||
|
},
|
||||||
|
resources: Object,
|
||||||
|
// The fragment to load.
|
||||||
|
translationFragment: String,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async ready() {
|
||||||
|
super.ready();
|
||||||
|
|
||||||
|
if (this.resources) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.translationFragment) {
|
||||||
|
// In dev mode, we will issue a warning if after a second we are still
|
||||||
|
// not configured correctly.
|
||||||
|
if (__DEV__) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
setTimeout(() => !this.resources && console.error(
|
||||||
|
'Forgot to pass in resources or set translationFragment for',
|
||||||
|
this.nodeName
|
||||||
|
), 1000);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { language, data } = await getTranslation(this.translationFragment);
|
||||||
|
this.resources = {
|
||||||
|
[language]: data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
@ -1,4 +1,3 @@
|
|||||||
import '@polymer/iron-flex-layout/iron-flex-layout-classes.js';
|
|
||||||
import '@polymer/polymer/lib/elements/dom-if.js';
|
import '@polymer/polymer/lib/elements/dom-if.js';
|
||||||
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
import '@polymer/polymer/lib/elements/dom-repeat.js';
|
||||||
import '@polymer/paper-input/paper-input.js';
|
import '@polymer/paper-input/paper-input.js';
|
||||||
@ -6,40 +5,16 @@ import '@polymer/paper-button/paper-button.js';
|
|||||||
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
import { html } from '@polymer/polymer/lib/utils/html-tag.js';
|
||||||
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
|
||||||
import hassCallApi from '../util/hass-call-api.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);
|
const callApi = (method, path, data) => hassCallApi('', {}, method, path, data);
|
||||||
|
|
||||||
class HaOnboarding extends PolymerElement {
|
class HaOnboarding extends localizeLiteMixin(PolymerElement) {
|
||||||
static get template() {
|
static get template() {
|
||||||
return html`
|
return html`
|
||||||
<style include="iron-flex iron-positioning"></style>
|
|
||||||
<style>
|
<style>
|
||||||
.content {
|
|
||||||
padding: 20px 16px;
|
|
||||||
max-width: 400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.96em;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header img {
|
|
||||||
margin-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
color: red;
|
color: red;
|
||||||
font-weight: bold;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
@ -47,54 +22,56 @@ class HaOnboarding extends PolymerElement {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="content layout vertical fit">
|
|
||||||
<div class='header'>
|
|
||||||
<img src="/static/icons/favicon-192x192.png" height="52">
|
|
||||||
Home Assistant
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<p>
|
||||||
<p>Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?</p>
|
[[localize('ui.panel.page-onboarding.intro')]]
|
||||||
<p>Let's get started by creating a user account.</p>
|
</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
<template is='dom-if' if='[[_error]]'>
|
<p>
|
||||||
<p class='error'>[[_error]]</p>
|
[[localize('ui.panel.page-onboarding.user.intro')]]
|
||||||
</template>
|
</p>
|
||||||
|
|
||||||
|
<template is='dom-if' if='[[_errorMsg]]'>
|
||||||
|
<p class='error'>[[_computeErrorMsg(localize, _errorMsg)]]</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<form>
|
||||||
<paper-input
|
<paper-input
|
||||||
autofocus
|
autofocus
|
||||||
label='Name'
|
label="[[localize('ui.panel.page-onboarding.user.data.name')]]"
|
||||||
value='{{_name}}'
|
value='{{_name}}'
|
||||||
required
|
required
|
||||||
auto-validate
|
auto-validate
|
||||||
error-message='Required'
|
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
|
||||||
on-blur='_maybePopulateUsername'
|
on-blur='_maybePopulateUsername'
|
||||||
></paper-input>
|
></paper-input>
|
||||||
|
|
||||||
<paper-input
|
<paper-input
|
||||||
label='Username'
|
label="[[localize('ui.panel.page-onboarding.user.data.username')]]"
|
||||||
value='{{_username}}'
|
value='{{_username}}'
|
||||||
required
|
required
|
||||||
auto-validate
|
auto-validate
|
||||||
error-message='Required'
|
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
|
||||||
></paper-input>
|
></paper-input>
|
||||||
|
|
||||||
<paper-input
|
<paper-input
|
||||||
label='Password'
|
label="[[localize('ui.panel.page-onboarding.user.data.password')]]"
|
||||||
value='{{_password}}'
|
value='{{_password}}'
|
||||||
required
|
required
|
||||||
type='password'
|
type='password'
|
||||||
auto-validate
|
auto-validate
|
||||||
error-message='Required'
|
error-message="[[localize('ui.panel.page-onboarding.user.required_field')]]"
|
||||||
></paper-input>
|
></paper-input>
|
||||||
|
|
||||||
<template is='dom-if' if='[[!_loading]]'>
|
<template is='dom-if' if='[[!_loading]]'>
|
||||||
<p class='action'>
|
<p class='action'>
|
||||||
<paper-button raised on-click='_submitForm'>Create Account</paper-button>
|
<paper-button raised on-click='_submitForm'>
|
||||||
|
[[localize('ui.panel.page-onboarding.user.create_account')]]
|
||||||
|
</paper-button>
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +83,12 @@ class HaOnboarding extends PolymerElement {
|
|||||||
_loading: {
|
_loading: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
value: false,
|
value: false,
|
||||||
}
|
},
|
||||||
|
translationFragment: {
|
||||||
|
type: String,
|
||||||
|
value: 'page-onboarding',
|
||||||
|
},
|
||||||
|
_errorMsg: String,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,10 +99,24 @@ class HaOnboarding extends PolymerElement {
|
|||||||
this._submitForm();
|
this._submitForm();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const steps = await callApi('get', 'onboarding');
|
|
||||||
if (steps.every(step => step.done)) {
|
try {
|
||||||
// Onboarding is done!
|
const response = await window.stepsPromise;
|
||||||
document.location = '/';
|
|
||||||
|
if (response.status === 404) {
|
||||||
|
// We don't load the component when onboarding is done
|
||||||
|
document.location = '/';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const steps = await response.json();
|
||||||
|
|
||||||
|
if (steps.every(step => step.done)) {
|
||||||
|
// Onboarding is done!
|
||||||
|
document.location = '/';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert('Something went wrong loading loading onboarding, try refreshing');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +131,12 @@ class HaOnboarding extends PolymerElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _submitForm() {
|
async _submitForm() {
|
||||||
if (!this._name || !this._username || !this._password) return;
|
if (!this._name || !this._username || !this._password) {
|
||||||
|
this._errorMsg = 'required_fields';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._errorMsg = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await callApi('post', 'onboarding/users', {
|
await callApi('post', 'onboarding/users', {
|
||||||
@ -150,9 +151,13 @@ class HaOnboarding extends PolymerElement {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
_loading: false,
|
_loading: false,
|
||||||
_error: err.message,
|
_errorMsg: err.message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_computeErrorMsg(localize, errorMsg) {
|
||||||
|
return localize(`ui.panel.page-onboarding.user.error.${errorMsg}`) || errorMsg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
customElements.define('ha-onboarding', HaOnboarding);
|
customElements.define('ha-onboarding', HaOnboarding);
|
||||||
|
@ -752,6 +752,75 @@
|
|||||||
"clear_completed": "Clear completed",
|
"clear_completed": "Clear completed",
|
||||||
"add_item": "Add item",
|
"add_item": "Add item",
|
||||||
"microphone_tip": "Tap the microphone on the top right and say “Add candy to my shopping list”"
|
"microphone_tip": "Tap the microphone on the top right and say “Add candy to my shopping list”"
|
||||||
|
},
|
||||||
|
"page-authorize": {
|
||||||
|
"initializing": "Initializing",
|
||||||
|
"authorizing_client": "You're about to give {clientId} access to your Home Assistant instance.",
|
||||||
|
"logging_in_with": "Logging in with **{authProviderName}**.",
|
||||||
|
"pick_auth_provider": "Or log in with",
|
||||||
|
"abort_intro": "Login aborted",
|
||||||
|
"form": {
|
||||||
|
"working": "Please wait",
|
||||||
|
"unknown_error": "Something went wrong",
|
||||||
|
"providers": {
|
||||||
|
"homeassistant": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_auth": "Invalid username or password"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"legacy_api_password": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"password": "API Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_auth": "Invalid API password"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"no_api_password_set": "You don't have an API password configured."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"trusted_networks": {
|
||||||
|
"step": {
|
||||||
|
"init": {
|
||||||
|
"data": {
|
||||||
|
"user": "User"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"not_whitelisted": "Your computer is not whitelisted."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page-onboarding": {
|
||||||
|
"intro": "Are you ready to awaken your home, reclaim your privacy and join a worldwide community of tinkerers?",
|
||||||
|
"user": {
|
||||||
|
"intro": "Let's get started by creating a user account.",
|
||||||
|
"required_field": "Required",
|
||||||
|
"data": {
|
||||||
|
"name": "Name",
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password"
|
||||||
|
},
|
||||||
|
"create_account": "Create Account",
|
||||||
|
"error": {
|
||||||
|
"required_fields": "Fill in all required fields"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,11 @@ const VERSION = version[0];
|
|||||||
const generateJSPage = (entrypoint, latestBuild) => {
|
const generateJSPage = (entrypoint, latestBuild) => {
|
||||||
return new HtmlWebpackPlugin({
|
return new HtmlWebpackPlugin({
|
||||||
inject: false,
|
inject: false,
|
||||||
template: './src/html/extra_page.html.template',
|
template: `./src/html/${entrypoint}.html.template`,
|
||||||
// Default templateParameterGenerator code
|
// Default templateParameterGenerator code
|
||||||
// https://github.com/jantimon/html-webpack-plugin/blob/master/index.js#L719
|
// https://github.com/jantimon/html-webpack-plugin/blob/master/index.js#L719
|
||||||
templateParameters: (compilation, assets, option) => ({
|
templateParameters: (compilation, assets, option) => ({
|
||||||
latestBuild,
|
latestBuild,
|
||||||
tag: `ha-${entrypoint}`,
|
|
||||||
compatibility: assets.chunks.compatibility.entry,
|
compatibility: assets.chunks.compatibility.entry,
|
||||||
entrypoint: assets.chunks[entrypoint].entry,
|
entrypoint: assets.chunks[entrypoint].entry,
|
||||||
hassIconsJS: assets.chunks['hass-icons'].entry,
|
hassIconsJS: assets.chunks['hass-icons'].entry,
|
||||||
|
@ -9790,6 +9790,10 @@ raw-body@2.3.2:
|
|||||||
iconv-lite "0.4.19"
|
iconv-lite "0.4.19"
|
||||||
unpipe "1.0.0"
|
unpipe "1.0.0"
|
||||||
|
|
||||||
|
raw-loader@^0.5.1:
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa"
|
||||||
|
|
||||||
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
|
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
|
||||||
version "1.2.7"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.7.tgz#8a10ca30d588d00464360372b890d06dacd02297"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.7.tgz#8a10ca30d588d00464360372b890d06dacd02297"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user