Compare commits

..

11 Commits

Author SHA1 Message Date
Ludeeus
50047e2f10 Adjust gap 2021-07-30 10:21:27 +00:00
Ludeeus
2811541fba update 2021-06-02 21:22:34 +00:00
Ludeeus
57788cec44 style 2021-06-02 17:02:24 +00:00
Ludeeus
0aa314d9ae overlay 2021-06-02 16:57:41 +00:00
Ludeeus
f8d97735b8 update 2021-06-02 16:09:28 +00:00
Ludeeus
830136b874 icons 2021-06-02 15:42:31 +00:00
Ludeeus
dd01710784 addon 2021-06-02 15:31:05 +00:00
Ludeeus
f7d0736731 init 2021-06-02 15:10:18 +00:00
Bram Kragten
fe5f9576c6 Fix dev 2021-06-02 10:11:29 +02:00
Brynley McDonald
1b282b65b7 Add QR code to long lived access tokens dialog (#8948)
* Add QR code to long lived access tokens dialog

* Apply suggestions from code review

Co-authored-by: Bram Kragten <mail@bramkragten.nl>

* Further changes from code review

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2021-06-02 09:59:22 +02:00
GitHub Action
e49664bad3 Translation update 2021-06-02 04:10:37 +00:00
15 changed files with 331 additions and 54 deletions

View File

@@ -1,4 +1,4 @@
import { mdiArrowUpBoldCircle, mdiPuzzle } from "@mdi/js";
import { mdiArrowUpBoldCircle, mdiPlay, mdiPuzzle, mdiStop } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
@@ -17,7 +17,36 @@ class HassioAddons extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) public narrow!: boolean;
protected render(): TemplateResult {
return html`<ha-card
.header=${this.supervisor.localize("dashboard.addons")}
>
<div class="addons" ?narrow=${this.narrow}>
${this.supervisor.supervisor.addons.map(
(addon) => html`<div
class="addon"
@click=${this._addonTapped}
.addon=${addon}
>
<div class="icon">
<div class="overlay">
<ha-svg-icon
.title=${addon.state}
.path=${addon.state === "started" ? mdiPlay : mdiStop}
>
</ha-svg-icon>
</div>
${addon.icon && atLeastVersion(this.hass.config.version, 0, 105)
? html`<img src="/api/hassio/addons/${addon.slug}/icon" />`
: html`<ha-svg-icon .path=${mdiPuzzle}></ha-svg-icon>`}
</div>
<div class="name">${addon.name}</div>
</div>`
)}
</div>
</ha-card>`;
return html`
<div class="content">
<h1>${this.supervisor.localize("dashboard.addons")}</h1>
@@ -88,9 +117,42 @@ class HassioAddons extends LitElement {
haStyle,
hassioStyle,
css`
ha-card {
.addons {
display: grid;
grid-template-columns: repeat(4, auto);
padding-bottom: 16px;
}
.addons[narrow] {
grid-template-columns: repeat(2, auto);
}
.addon {
text-align: center;
max-width: 100px;
padding: 0 8px;
cursor: pointer;
}
.icon > *:not(.overlay) {
position: relative;
max-height: 60px;
max-width: 60px;
margin: auto;
--mdc-icon-size: 60px;
display: flex;
}
.icon {
margin-bottom: 4px;
}
.overlay {
position: absolute;
z-index: 2;
--mdc-icon-size: 24px;
color: var(--secondary-text-color);
background-color: var(--secondary-background-color);
opacity: 0.6;
border-radius: 100%;
margin-left: 12px;
border: 1px var(--secondary-text-color) solid;
}
`,
];
}

View File

@@ -5,6 +5,7 @@ import "../../../src/layouts/hass-tabs-subpage";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
import "./hassio-addons";
import "./hassio-update";
@@ -32,14 +33,17 @@ class HassioDashboard extends LitElement {
<span slot="header">
${this.supervisor.localize("panel.dashboard")}
</span>
<div class="content">
<hassio-update
.hass=${this.hass}
.supervisor=${this.supervisor}
.narrow=${this.narrow}
></hassio-update>
<hassio-addons
.hass=${this.hass}
.supervisor=${this.supervisor}
.narrow=${this.narrow}
></hassio-addons>
</div>
</hass-tabs-subpage>
@@ -49,9 +53,18 @@ class HassioDashboard extends LitElement {
static get styles(): CSSResultGroup {
return [
haStyle,
hassioStyle,
css`
.content {
margin: 0 auto;
display: grid;
max-width: 1400px;
justify-content: center;
grid-template-columns: repeat(2, auto);
gap: 16px;
}
.content > * {
display: block;
min-width: 400px;
}
`,
];

View File

@@ -1,5 +1,5 @@
import "@material/mwc-button";
import { mdiHomeAssistant } from "@mdi/js";
import { mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -42,11 +42,15 @@ export class HassioUpdate extends LitElement {
@property({ attribute: false }) public supervisor!: Supervisor;
@property({ type: Boolean }) public narrow!: boolean;
private _pendingUpdates = memoizeOne(
(supervisor: Supervisor): number =>
Object.keys(supervisor).filter(
(value) => supervisor[value].update_available
).length
).length +
supervisor.supervisor.addons.filter((addon) => addon.update_available)
.length
);
protected render(): TemplateResult {
@@ -60,15 +64,38 @@ export class HassioUpdate extends LitElement {
}
return html`
<div class="content">
<h1>
${this.supervisor.localize(
"common.update_available",
"count",
updatesAvailable
<ha-card
.header="${this.supervisor.localize(
"common.update_available",
"count",
updatesAvailable + 1
)}
🎉"
>
${this._renderUpdateRow({
type: "os",
heading: "Home Assistant Operating system",
icon: mdiHomeAssistant,
version: "5",
version_latest: "6",
})}
${this.supervisor.addon.addons
.filter((addon) => addon.update_available)
.map((addon) =>
this._renderUpdateRow({
type: "addon",
heading: addon.name,
version: addon.version_latest,
version_latest: addon.version,
image: addon.icon
? `/api/hassio/addons/${addon.slug}/icon`
: undefined,
icon: mdiPuzzle,
})
)}
🎉
</h1>
</ha-card>
<div class="content">
<h1></h1>
<div class="card-group">
${this._renderUpdateCard(
"Home Assistant Core",
@@ -100,6 +127,37 @@ export class HassioUpdate extends LitElement {
`;
}
private _renderUpdateRow(options: {
type: "supervisor" | "os" | "core" | "addon";
heading: string;
version: string;
version_latest: string;
icon?: string;
image?: string;
release_notes?: string;
slug?: string;
}): TemplateResult {
return html`<div class="update-row">
<paper-icon-item>
<div class="icon" slot="item-icon">
${options.image && atLeastVersion(this.hass.config.version, 0, 104)
? html`<img src="${options.image}" />`
: options.icon
? html`<ha-svg-icon .path=${options.icon}></ha-svg-icon>`
: ""}
</div>
<paper-item-body two-line>
${options.heading}
<div secondary>Version ${options.version_latest} is available</div>
</paper-item-body>
</paper-icon-item>
<div class="update-row-actions" ?narrow=${false}>
<mwc-button>Releaese notes</mwc-button>
<mwc-button>Update</mwc-button>
</div>
</div>`;
}
private _renderUpdateCard(
name: string,
key: string,
@@ -231,31 +289,21 @@ export class HassioUpdate extends LitElement {
haStyle,
hassioStyle,
css`
.icon {
--mdc-icon-size: 48px;
float: right;
margin: 0 0 2px 10px;
color: var(--primary-text-color);
.update-row,
paper-icon-item {
display: flex;
align-items: center;
}
.update-heading {
font-size: var(--paper-font-subhead_-_font-size);
font-weight: 500;
margin-bottom: 0.5em;
color: var(--primary-text-color);
.update-row {
padding: 8px;
justify-content: space-between;
}
.card-content {
height: calc(100% - 47px);
box-sizing: border-box;
}
.card-actions {
text-align: right;
}
a {
text-decoration: none;
}
ha-settings-row {
padding: 0;
--paper-item-body-two-line-min-height: 32px;
.icon > * {
max-height: 32px;
max-width: 32px;
margin-right: 16px;
--mdc-icon-size: 32px;
}
`,
];

View File

@@ -0,0 +1,106 @@
import "@material/mwc-button";
import "@polymer/paper-input/paper-input";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../common/dom/fire_event";
import { createCloseHeading } from "../../components/ha-dialog";
import { haStyleDialog } from "../../resources/styles";
import type { HomeAssistant } from "../../types";
import { LongLivedAccessTokenDialogParams } from "./show-long-lived-access-token-dialog";
const QR_LOGO_URL = "/static/icons/favicon-192x192.png";
@customElement("ha-long-lived-access-token-dialog")
export class HaLongLivedAccessTokenDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: LongLivedAccessTokenDialogParams;
@state() private _qrCode?: TemplateResult;
public showDialog(params: LongLivedAccessTokenDialogParams): void {
this._params = params;
}
public closeDialog() {
this._params = undefined;
this._qrCode = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._params || !this._params.token) {
return html``;
}
return html`
<ha-dialog
open
hideActions
.heading=${createCloseHeading(this.hass, this._params.name)}
@closed=${this.closeDialog}
>
<div>
<paper-input
dialogInitialFocus
.value=${this._params.token}
.label=${this.hass.localize(
"ui.panel.profile.long_lived_access_tokens.prompt_copy_token"
)}
type="text"
></paper-input>
<div id="qr">
${this._qrCode
? this._qrCode
: html`
<mwc-button @click=${this._generateQR}>
Generate QR code
</mwc-button>
`}
</div>
</div>
</ha-dialog>
`;
}
private async _generateQR() {
const qrcode = await import("qrcode");
const canvas = await qrcode.toCanvas(this._params?.token, {
width: 180,
errorCorrectionLevel: "Q",
});
const context = canvas.getContext("2d");
const imageObj = new Image();
imageObj.src = QR_LOGO_URL;
await new Promise((resolve) => {
imageObj.onload = resolve;
});
context.drawImage(
imageObj,
canvas.width / 3,
canvas.height / 3,
canvas.width / 3,
canvas.height / 3
);
this._qrCode = html`<img src=${canvas.toDataURL()}></img>`;
}
static get styles(): CSSResultGroup {
return [
haStyleDialog,
css`
#qr {
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"ha-long-lived-access-token-dialog": HaLongLivedAccessTokenDialog;
}
}

View File

@@ -18,6 +18,7 @@ import {
import { haStyle } from "../../resources/styles";
import "../../styles/polymer-ha-style";
import { HomeAssistant } from "../../types";
import { showLongLivedAccessTokenDialog } from "./show-long-lived-access-token-dialog";
@customElement("ha-long-lived-access-tokens-card")
class HaLongLivedTokens extends LitElement {
@@ -118,13 +119,7 @@ class HaLongLivedTokens extends LitElement {
client_name: name,
});
showPromptDialog(this, {
title: name,
text: this.hass.localize(
"ui.panel.profile.long_lived_access_tokens.prompt_copy_token"
),
defaultValue: token,
});
showLongLivedAccessTokenDialog(this, { token, name });
fireEvent(this, "hass-refresh-tokens");
} catch (err) {

View File

@@ -0,0 +1,17 @@
import { fireEvent } from "../../common/dom/fire_event";
export interface LongLivedAccessTokenDialogParams {
token: string;
name: string;
}
export const showLongLivedAccessTokenDialog = (
element: HTMLElement,
longLivedAccessTokenDialogParams: LongLivedAccessTokenDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "ha-long-lived-access-token-dialog",
dialogImport: () => import("./ha-long-lived-access-token-dialog"),
dialogParams: longLivedAccessTokenDialogParams,
});
};

View File

@@ -929,8 +929,11 @@
},
"dialogs": {
"config_entry_system_options": {
"enable_new_entities_description": "Si es desactiva, les entitats descobertes recentment per a {integration} no s'afegiran automàticament a Home Assistant.",
"enable_new_entities_description": "Si esactivat, les noves entitats descobertes per {integration} s'afegiran automàticament a Home Assistant.",
"enable_new_entities_label": "Activa entitats afegides recentment.",
"enable_polling_description": "Si Home Assistant ha de sondejar automàticament {integration} per obtenir actualitzacions de les entitats.",
"enable_polling_label": "Activa el sondeig per obtenir actualitzacions.",
"restart_home_assistant": "Has de reiniciar Home Assistant per aplicar els canvis.",
"title": "Opcions del sistema per a {integration}",
"update": "Actualitza"
},
@@ -1540,7 +1543,7 @@
"delete_confirm": "Segur que vols eliminar-ho?",
"duplicate": "Duplica",
"header": "Disparadors",
"introduction": "Els activadors són les regles que fan que es dispari una automatització. Pots definir més d'un activador per a cada automatització. Una vegada s'iniciï un activador, Home Assistant validarà les condicions (si n'hi ha) i finalment cridarà l'acció.",
"introduction": "Els disparadors són les regles que fan que s'executi una automatització. Pots definir més d'un disparador per a cada automatització. Una vegada salti un disparador, Home Assistant validarà les condicions (si n'hi ha) i finalment cridarà l'acció.",
"learn_more": "Més informació sobre els activadors",
"name": "Disparador",
"type_select": "Tipus de disparador",
@@ -2073,7 +2076,8 @@
"scripts": "Programes (scripts)",
"unknown_error": "Error desconegut",
"unnamed_device": "Dispositiu sense nom",
"update": "Actualitza"
"update": "Actualitza",
"update_device_error": "No s'ha pogut actualitzar el dispositiu"
},
"entities": {
"caption": "Entitats",
@@ -2206,6 +2210,7 @@
"depends_on_cloud": "Depèn del núvol",
"device_unavailable": "Dispositiu no disponible",
"devices": "{count} {count, plural,\n one {dispositiu}\n other {dispositius}\n}",
"disable_error": "S'ha produït un error en activar o desactivar la integració",
"disable_restart_confirm": "Reinicia Home Assistant per acabar de desactivar aquesta integració",
"disable": {
"disable_confirm": "Estàs segur que vols desactivar aquesta entrada de configuració? Els seus dispositius i entitats es desactivaran.",
@@ -2217,6 +2222,7 @@
},
"disabled_cause": "Desactivada per {cause}"
},
"disabled_polling": "Sondeig automàtic per l'obtenció de dades actualitzades desactivat",
"documentation": "Documentació",
"enable_restart_confirm": "Reinicia Home Assistant per acabar d'activar aquesta integració",
"entities": "{count} {count, plural,\n one {entitat}\n other {entitats}\n}",

View File

@@ -2076,7 +2076,8 @@
"scripts": "Scripts",
"unknown_error": "Unknown error",
"unnamed_device": "Unnamed device",
"update": "Update"
"update": "Update",
"update_device_error": "Updating the device failed"
},
"entities": {
"caption": "Entities",
@@ -2209,6 +2210,7 @@
"depends_on_cloud": "Depends on the cloud",
"device_unavailable": "Device unavailable",
"devices": "{count} {count, plural,\n one {device}\n other {devices}\n}",
"disable_error": "Enabling or disabling of the integration failed",
"disable_restart_confirm": "Restart Home Assistant to finish disabling this integration",
"disable": {
"disable_confirm": "Are you sure you want to disable this config entry? Its devices and entities will be disabled.",

View File

@@ -929,8 +929,11 @@
},
"dialogs": {
"config_entry_system_options": {
"enable_new_entities_description": "Si está deshabilitada, las nuevas entidades que se descubran para {integration} no se añadirán automáticamente a Home Assistant.",
"enable_new_entities_description": "Si los dispositivos recién descubiertos para {integration} deberían agregarse automáticamente.",
"enable_new_entities_label": "Habilitar entidades recién añadidas.",
"enable_polling_description": "Si Home Assistant debería sondear automáticamente las entidades de {integration} para obtener actualizaciones.",
"enable_polling_label": "Habilitar el sondeo de actualizaciones.",
"restart_home_assistant": "Debes reiniciar Home Assistant para que los cambios surtan efecto.",
"title": "Opciones del sistema para {integration}",
"update": "Actualizar"
},
@@ -2217,6 +2220,7 @@
},
"disabled_cause": "Deshabilitada por {cause}"
},
"disabled_polling": "Sondeo automático para datos actualizados deshabilitado",
"documentation": "Documentación",
"enable_restart_confirm": "Reinicia Home Assistant para terminar de habilitar esta integración",
"entities": "{count} {count, plural,\n one {entidad}\n other {entidades}\n}",

View File

@@ -929,8 +929,11 @@
},
"dialogs": {
"config_entry_system_options": {
"enable_new_entities_description": "Kui see on keelatud ei lisata äsja avastatud sidumise {integration} olemeid automaatselt Home Assistant'i.",
"enable_new_entities_description": "Kas lisada äsja avastatud sidumise {integration} olemed automaatselt Home Assistant'i.",
"enable_new_entities_label": "Luba äsja lisatud olemid.",
"enable_polling_description": "Kas Home Assistant peaks automaatselt küsitlema {integration} üksusi uuenduste saamiseks.",
"enable_polling_label": "Luba värskenduste jaoks küsitlus.",
"restart_home_assistant": "Muudatuste jõustumiseks pead Home Assistanti taaskäivitama.",
"title": "Süsteemi valikud {integration} jaoks",
"update": "Uuenda"
},
@@ -2217,6 +2220,7 @@
},
"disabled_cause": "Keelatud {cause} põhjusel"
},
"disabled_polling": "Uuendatud andmete automaatne päring on välja lülitatud",
"documentation": "Vaata dokumentatsiooni",
"enable_restart_confirm": "Selle sidumise lubamise lõpetamiseks taaskäivita Home Assistant",
"entities": "{count} {count, plural,\n one {olem}\n other{olemit}\n}",

View File

@@ -931,6 +931,9 @@
"config_entry_system_options": {
"enable_new_entities_description": "Se disabilitato, le entità appena rilevate per {integration} non verranno automaticamente aggiunte a Home Assistant.",
"enable_new_entities_label": "Abilita nuove entità aggiunte.",
"enable_polling_description": "Se disabilitato, Home Assistant non richiederà più in automatico aggiornamenti alle entità dell'integrazione {integration}.",
"enable_polling_label": "Abilita il polling per gli aggiornamenti.",
"restart_home_assistant": "È necessario riavviare Home Assistant affinché le modifiche abbiano effetto.",
"title": "Opzioni di sistema per {integration}",
"update": "Aggiorna"
},
@@ -2215,8 +2218,9 @@
"integration": "Integrazione",
"user": "utente"
},
"disabled_cause": "Disabilitato da {causa}"
"disabled_cause": "Disabilitato da {cause}"
},
"disabled_polling": "Polling automatico per i dati aggiornati disabilitato",
"documentation": "Documentazione",
"enable_restart_confirm": "Riavvia Home Assistant per completare l'attivazione di questa integrazione",
"entities": "{count} {count, plural, \none {entità}\nother {entità }\n}",

View File

@@ -931,6 +931,9 @@
"config_entry_system_options": {
"enable_new_entities_description": "Indien uitgeschakeld, worden nieuwe entiteiten van {integration} niet automatisch aan Home Assistant toegevoegd.",
"enable_new_entities_label": "Voeg nieuwe entiteiten automatisch toe",
"enable_polling_description": "Of Home Assistant automatisch moet pollen voor updates voor {integration} entiteiten",
"enable_polling_label": "Schakel polling voor updates in.",
"restart_home_assistant": "U moet Home Assistant opnieuw starten om uw wijzigingen door te voeren.",
"title": "Systeeminstellingen voor {integration}",
"update": "Bijwerken"
},
@@ -2217,6 +2220,7 @@
},
"disabled_cause": "Uitgeschakeld door {cause}"
},
"disabled_polling": "Automatische polling voor bijgewerkte gegevens uitgeschakeld",
"documentation": "Documentatie",
"enable_restart_confirm": "Start Home Assistant opnieuw op om het opzetten van deze integratie te voltooien",
"entities": "{count} {count, plural,\n one {entiteit}\n other {entiteiten}\n}",

View File

@@ -929,8 +929,11 @@
},
"dialogs": {
"config_entry_system_options": {
"enable_new_entities_description": "{integration} будет автоматически добавлять в Home Assistant вновь обнаруженные объекты",
"enable_new_entities_description": "Home Assistant будет автоматически добавлять новые обнаруженные устройства {integration}",
"enable_new_entities_label": "Добавлять новые объекты",
"enable_polling_description": "Home Assistant будет автоматически опрашивать объекты {integration} для получения обновлений",
"enable_polling_label": "Включить опрос для получения обновлений",
"restart_home_assistant": "Чтобы изменения вступили в силу, нужно перезапустить Home Assistant.",
"title": "{integration}",
"update": "Обновить"
},
@@ -2217,6 +2220,7 @@
},
"disabled_cause": "Деактивировано {cause}"
},
"disabled_polling": "Автоматический опрос для получения обновленных данных отключен",
"documentation": "Документация",
"enable_restart_confirm": "Перезапустите Home Assistant, чтобы завершить подключение этой интеграции.",
"entities": "{count} {count, plural,\n one {объект}\n few {объекта}\n many {объектов}\n other {объектов}\n}",

View File

@@ -929,8 +929,11 @@
},
"dialogs": {
"config_entry_system_options": {
"enable_new_entities_description": "如果禁用,从 {integration} 新发现的实体将不会自动添加到 Home Assistant。",
"enable_new_entities_description": "是否将 {integration} 新发现的实体自动添加到 Home Assistant。",
"enable_new_entities_label": "启用新添加的实体。",
"enable_polling_description": "是否让 Home Assistant 轮询 {integration} 的实体,以便更新其状态。",
"enable_polling_label": "启用轮询刷新。",
"restart_home_assistant": "需要重新启动 Home Assistant 才能使更改生效。",
"title": "{integration} 系统选项",
"update": "更新"
},
@@ -2115,7 +2118,7 @@
"confirm_title": "您要删除这 {number} 个实体吗?"
},
"search": "搜索实体",
"selected": "选择 {number} 项",
"selected": "选择 {number} 项",
"status": {
"disabled": "已禁用",
"ok": "确定",
@@ -2217,6 +2220,7 @@
},
"disabled_cause": "通过{cause}禁用"
},
"disabled_polling": "自动轮询刷新数据已禁用",
"documentation": "文档",
"enable_restart_confirm": "重启 Home Assistant 以完成此集成的启用",
"entities": "{count} {count, plural,\n one {个实体}\n other {个实体}\n}",

View File

@@ -929,8 +929,11 @@
},
"dialogs": {
"config_entry_system_options": {
"enable_new_entities_description": "關閉後,{integration} 新發現的實體將不會自動新增至 Home Assistant。",
"enable_new_entities_description": "關閉後,{integration} 新發現的實體將不會自動新增。",
"enable_new_entities_label": "啟用新增實體",
"enable_polling_description": "是否讓 Home Assistant 自動輪詢 {integration} 實體以獲得更新。",
"enable_polling_label": "開啟自動輪詢更新。",
"restart_home_assistant": "需要重啟 Home Assistant 才能使變更生效。",
"title": "{integration} 系統選項",
"update": "更新"
},
@@ -2217,6 +2220,7 @@
},
"disabled_cause": "由 {cause} 關閉。"
},
"disabled_polling": "自動輪詢更新資料已關閉。",
"documentation": "相關文件",
"enable_restart_confirm": "重啟 Home Assistant 以完成整合開啟",
"entities": "{count} {count, plural,\n one {個實體}\n other {個實體}\n}",