Compare commits

..

1 Commits

Author SHA1 Message Date
Paulus Schoutsen
0ec7973e95 Exclude viz network from babel 2021-05-31 13:36:24 -07:00
786 changed files with 33848 additions and 63353 deletions

View File

@@ -1,10 +1,9 @@
{
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"plugin:@typescript-eslint/recommended",
"plugin:wc/recommended",
"plugin:lit/all",
"plugin:lit/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
@@ -36,51 +35,55 @@
"es6": true
},
"rules": {
"class-methods-use-this": "off",
"new-cap": "off",
"prefer-template": "off",
"object-shorthand": "off",
"func-names": "off",
"no-underscore-dangle": "off",
"strict": "off",
"no-plusplus": "off",
"no-bitwise": "error",
"comma-dangle": "off",
"vars-on-top": "off",
"no-continue": "off",
"no-param-reassign": "off",
"no-multi-assign": "off",
"no-console": "error",
"radix": "off",
"no-alert": "off",
"no-nested-ternary": "off",
"prefer-destructuring": "off",
"class-methods-use-this": 0,
"new-cap": 0,
"prefer-template": 0,
"object-shorthand": 0,
"func-names": 0,
"prefer-arrow-callback": 0,
"no-underscore-dangle": 0,
"strict": 0,
"prefer-spread": 0,
"no-plusplus": 0,
"no-bitwise": 2,
"comma-dangle": 0,
"vars-on-top": 0,
"no-continue": 0,
"no-param-reassign": 0,
"no-multi-assign": 0,
"no-console": 2,
"radix": 0,
"no-alert": 0,
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off",
"import/prefer-default-export": "off",
"import/no-default-export": "off",
"import/no-unresolved": "off",
"import/no-cycle": "off",
"prefer-promise-reject-errors": 0,
"import/order": 0,
"import/prefer-default-export": 0,
"import/no-unresolved": 0,
"import/no-cycle": 0,
"import/extensions": [
"error",
2,
"ignorePackages",
{ "ts": "never", "js": "never" }
],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": "off",
"default-case": "off",
"wc/no-self-class": "off",
"no-shadow": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"object-curly-newline": 0,
"default-case": 0,
"wc/no-self-class": 0,
"no-shadow": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [
"off",
0,
{
"selector": "default",
"format": ["camelCase", "snake_case"],
@@ -98,22 +101,8 @@
"format": ["PascalCase"]
}
],
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-vars": [
"error",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
],
"unused-imports/no-unused-imports": "error",
"lit/attribute-value-entities": "off",
"lit/no-template-map": "off",
"lit/no-template-arrow": "warn"
"lit/attribute-value-entities": 0
},
"plugins": ["disable", "unused-imports"],
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"processor": "disable/disable"
}

View File

@@ -10,21 +10,26 @@ on:
- dev
- master
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:
@@ -37,35 +42,51 @@ jobs:
run: yarn run lint:types
- name: Run prettier
run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
test:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:
CI: true
- name: Run Tests
run: yarn run test
- name: Run Mocha
run: npm run mocha
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:
@@ -80,11 +101,20 @@ jobs:
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:

View File

@@ -4,22 +4,26 @@ on:
push:
branches:
- dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out files from GitHub
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
- name: Setting up Node.js
uses: actions/setup-node@v1
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
node-version: 12.x
- name: Get yarn cache path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Fetching Yarn cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies
run: yarn install
env:

View File

@@ -6,9 +6,9 @@ on:
- published
env:
WHEELS_TAG: 3.8-alpine3.12
PYTHON_VERSION: 3.8
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
NODE_VERSION: 12.1
jobs:
release:
@@ -30,15 +30,7 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
cache: yarn
- name: Install dependencies
run: yarn install
- name: Download Translations
run: ./script/translations_download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
- name: Build and release package
run: |
python3 -m pip install twine
@@ -72,8 +64,6 @@ jobs:
strategy:
matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag:
- "3.9-alpine3.14"
steps:
- name: Download requirements.txt
uses: actions/download-artifact@v2
@@ -83,7 +73,7 @@ jobs:
- name: Build wheels
uses: home-assistant/wheels@master
with:
tag: ${{ matrix.tag }}
tag: ${{ env.WHEELS_TAG }}
arch: ${{ matrix.arch }}
wheels-host: ${{ secrets.WHEELS_HOST }}
wheels-key: ${{ secrets.WHEELS_KEY }}

View File

@@ -1,6 +1,8 @@
name: Translations
on:
schedule:
- cron: "30 0 * * *"
push:
branches:
- dev
@@ -8,7 +10,7 @@ on:
- src/translations/en.json
env:
NODE_VERSION: 14
NODE_VERSION: 12
jobs:
upload:
@@ -18,8 +20,46 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Upload Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base
download:
name: Download
needs: upload
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }}
uses: actions/setup-node@v2
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download Translations
run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
npm install
./script/translations_download
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
with:
name: GitHub Action
email: github-action@users.noreply.github.com
- name: Update translation
run: |
git add translations
git commit -am "Translation update"
git push

27
.gitignore vendored
View File

@@ -1,23 +1,10 @@
.DS_Store
.reify-cache
# build
build
build-translations/*
hass_frontend/*
dist
# yarn
.yarn/*
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
node_modules/*
yarn-error.log
npm-debug.log
.DS_Store
hass_frontend/*
.reify-cache
# Python stuff
*.py[cod]
@@ -27,8 +14,11 @@ npm-debug.log
# venv stuff
pyvenv.cfg
pip-selfcheck.json
venv/*
venv
.venv
lib
bin
dist
# vscode
.vscode/*
@@ -41,8 +31,9 @@ src/cast/dev_const.ts
# Secrets
.lokalise_token
yarn-error.log
# asdf
#asdf
.tool-versions
# Home Assistant config

4
.mocharc.cjs Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
require: "test-mocha/testconf.js",
timeout: 10000,
};

2
.nvmrc
View File

@@ -1 +1 @@
14
12.1

File diff suppressed because one or more lines are too long

View File

@@ -1,29 +0,0 @@
diff --git a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
index d92179f7fd5315203f870a6963e871dc8ddf6c0c..362e284121b97e0fba0925225777aebc32e26b8d 100644
--- a/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
+++ b/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js
@@ -1,14 +1,15 @@
-let _ET, ET;
+let _ET;
+let ET;
export default async function EventTarget() {
- return ET || init();
+ return ET || init();
}
async function init() {
- _ET = window.EventTarget;
- try {
- new _ET();
- }
- catch (_a) {
- _ET = (await import('event-target-shim')).EventTarget;
- }
- return (ET = _ET);
+ _ET = window.EventTarget;
+ try {
+ new _ET();
+ } catch (_a) {
+ _ET = (await import("event-target-shim")).default.EventTarget;
+ }
+ return (ET = _ET);
}

View File

@@ -1,34 +0,0 @@
diff --git a/lib/legacy/class.js b/lib/legacy/class.js
index aee2511be1cd9bf900ee552bc98190c1631c57c0..f2f499d68bf52034cac9c28307c99e8ce6b8417d 100644
--- a/lib/legacy/class.js
+++ b/lib/legacy/class.js
@@ -304,17 +304,23 @@ function GenerateClassFromInfo(info, Base, behaviors) {
// only proceed if the generated class' prototype has not been registered.
const generatedProto = PolymerGenerated.prototype;
if (!generatedProto.hasOwnProperty(JSCompiler_renameProperty('__hasRegisterFinished', generatedProto))) {
- generatedProto.__hasRegisterFinished = true;
+ // make sure legacy lifecycle is called on the *element*'s prototype
+ // and not the generated class prototype; if the element has been
+ // extended, these are *not* the same.
+ const proto = Object.getPrototypeOf(this);
+ // Only set flag when generated prototype itself is registered,
+ // as this element may be extended from, and needs to run `registered`
+ // on all behaviors on the subclass as well.
+ if (proto === generatedProto) {
+ generatedProto.__hasRegisterFinished = true;
+ }
// ensure superclass is registered first.
super._registered();
// copy properties onto the generated class lazily if we're optimizing,
- if (legacyOptimizations) {
+ if (legacyOptimizations && !Object.hasOwnProperty(generatedProto, '__hasCopiedProperties')) {
+ generatedProto.__hasCopiedProperties = true;
copyPropertiesToProto(generatedProto);
}
- // make sure legacy lifecycle is called on the *element*'s prototype
- // and not the generated class prototype; if the element has been
- // extended, these are *not* the same.
- const proto = Object.getPrototypeOf(this);
let list = lifecycle.beforeRegister;
if (list) {
for (let i=0; i < list.length; i++) {

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,9 +0,0 @@
nodeLinker: node-modules
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
spec: "@yarnpkg/plugin-interactive-tools"
yarnPath: .yarn/releases/yarn-3.0.2.cjs

View File

@@ -1,170 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require("path");
// Currently only supports CommonJS modules, as require is synchronous. `import` would need babel running asynchronous.
module.exports = function inlineConstants(babel, options, cwd) {
const t = babel.types;
if (!Array.isArray(options.modules)) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `modules` array to be passed"
);
}
if (options.resolveExtensions && !Array.isArray(options.resolveExtensions)) {
throw new TypeError(
"babel-plugin-inline-constants: expected `resolveExtensions` to be an array"
);
}
const ignoreModuleNotFound = options.ignoreModuleNotFound;
const resolveExtensions = options.resolveExtensions;
const hasRelativeModules = options.modules.some(
(module) => module.startsWith(".") || module.startsWith("/")
);
const modules = Object.fromEntries(
options.modules.map((module) => {
const absolute = module.startsWith(".")
? require.resolve(module, { paths: [cwd] })
: module;
// eslint-disable-next-line import/no-dynamic-require
return [absolute, require(absolute)];
})
);
const toLiteral = (value) => {
if (typeof value === "string") {
return t.stringLiteral(value);
}
if (typeof value === "number") {
return t.numericLiteral(value);
}
if (typeof value === "boolean") {
return t.booleanLiteral(value);
}
if (value === null) {
return t.nullLiteral();
}
throw new Error(
"babel-plugin-inline-constants: cannot handle non-literal `" + value + "`"
);
};
const resolveAbsolute = (value, state, resolveExtensionIndex) => {
if (!state.filename) {
throw new TypeError(
"babel-plugin-inline-constants: expected a `filename` to be set for files"
);
}
if (resolveExtensions && resolveExtensionIndex !== undefined) {
value += resolveExtensions[resolveExtensionIndex];
}
try {
return require.resolve(value, { paths: [path.dirname(state.filename)] });
} catch (error) {
if (
error.code === "MODULE_NOT_FOUND" &&
resolveExtensions &&
(resolveExtensionIndex === undefined ||
resolveExtensionIndex < resolveExtensions.length - 1)
) {
const resolveExtensionIdx = (resolveExtensionIndex || -1) + 1;
return resolveAbsolute(value, state, resolveExtensionIdx);
}
if (error.code === "MODULE_NOT_FOUND" && ignoreModuleNotFound) {
return undefined;
}
throw error;
}
};
const importDeclaration = (p, state) => {
if (p.node.type !== "ImportDeclaration") {
return;
}
const absolute =
hasRelativeModules && p.node.source.value.startsWith(".")
? resolveAbsolute(p.node.source.value, state)
: p.node.source.value;
if (!absolute || !(absolute in modules)) {
return;
}
const module = modules[absolute];
for (const specifier of p.node.specifiers) {
if (
specifier.type === "ImportDefaultSpecifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!("default" in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access default export from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module.default);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else if (
specifier.type === "ImportSpecifier" &&
specifier.imported &&
specifier.imported.type === "Identifier" &&
specifier.local &&
specifier.local.type === "Identifier"
) {
if (!(specifier.imported.name in module)) {
throw new Error(
"babel-plugin-inline-constants: cannot access `" +
specifier.imported.name +
"` from `" +
p.node.source.value +
"`"
);
}
const variableValue = toLiteral(module[specifier.imported.name]);
const variable = t.variableDeclarator(
t.identifier(specifier.local.name),
variableValue
);
p.insertBefore({
type: "VariableDeclaration",
kind: "const",
declarations: [variable],
});
} else {
throw new Error("Cannot handle specifier `" + specifier.type + "`");
}
}
p.remove();
};
return {
visitor: {
ImportDeclaration: importDeclaration,
},
};
};

View File

@@ -5,6 +5,8 @@ const paths = require("./paths.js");
// Files from NPM Packages that should not be imported
module.exports.ignorePackages = ({ latestBuild }) => [
// Bloats bundle and it's not used.
path.resolve(require.resolve("moment"), "../locale"),
// Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"),
];
@@ -18,8 +20,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/typography.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
// Compatibility not needed for latest builds
latestBuild &&
// wrapped in require.resolve so it blows up if file no longer exists
@@ -51,29 +52,17 @@ module.exports.terserOptions = (latestBuild) => ({
module.exports.babelOptions = ({ latestBuild }) => ({
babelrc: false,
compact: false,
presets: [
!latestBuild && [
"@babel/preset-env",
{
useBuiltIns: "entry",
corejs: "3.15",
bugfixes: true,
corejs: "3.6",
},
],
"@babel/preset-typescript",
].filter(Boolean),
plugins: [
[
path.resolve(
paths.polymer_dir,
"build-scripts/babel-plugins/inline-constants-plugin.js"
),
{
modules: ["@mdi/js"],
ignoreModuleNotFound: true,
},
],
// Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [
"@babel/plugin-proposal-object-rest-spread",
@@ -82,21 +71,21 @@ module.exports.babelOptions = ({ latestBuild }) => ({
// Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
"@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
].filter(Boolean),
exclude: [
// \\ for Windows, / for Mac OS and Linux
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
});
// Are already ES5, cause warnings when babelified.
module.exports.babelExclude = () => [
require.resolve("@mdi/js/mdi.js"),
require.resolve("hls.js"),
require.resolve("vis-network/peer/esm/vis-network.js"),
];
const outputPath = (outputRoot, latestBuild) =>
path.resolve(outputRoot, latestBuild ? "frontend_latest" : "frontend_es5");

View File

@@ -47,8 +47,8 @@ gulp.task(
gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests
...(env.isTest() ? [] : ["compress-app"]),
...// Don't compress running tests
(env.isTest() ? [] : ["compress-app"]),
gulp.parallel(
"gen-pages-prod",
"gen-index-app-prod",

View File

@@ -5,32 +5,32 @@ require("./translations");
gulp.task(
"clean",
gulp.parallel("clean-translations", () =>
del([paths.app_output_root, paths.build_dir])
)
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.app_output_root, paths.build_dir]);
})
);
gulp.task(
"clean-demo",
gulp.parallel("clean-translations", () =>
del([paths.demo_output_root, paths.build_dir])
)
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.demo_output_root, paths.build_dir]);
})
);
gulp.task(
"clean-cast",
gulp.parallel("clean-translations", () =>
del([paths.cast_output_root, paths.build_dir])
)
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.cast_output_root, paths.build_dir]);
})
);
gulp.task("clean-hassio", () =>
del([paths.hassio_output_root, paths.build_dir])
);
gulp.task("clean-hassio", function cleanOutputAndBuildDir() {
return del([paths.hassio_output_root, paths.build_dir]);
});
gulp.task(
"clean-gallery",
gulp.parallel("clean-translations", () =>
del([paths.gallery_output_root, paths.build_dir])
)
gulp.parallel("clean-translations", function cleanOutputAndBuildDir() {
return del([paths.gallery_output_root, paths.build_dir]);
})
);

View File

@@ -302,23 +302,15 @@ gulp.task("gen-index-hassio-prod", async () => {
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true });
// Safari 12 and below does not have a compliant ES2015 implementation of template literals, so we ship ES5
fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"),
`
function loadES5() {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try {
new Function("import('${latestEntrypoint}')")();
} catch (err) {
loadES5();
}
}
`,
{ encoding: "utf-8" }

View File

@@ -2,6 +2,7 @@
const gulp = require("gulp");
const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra");
const paths = require("../paths");
@@ -12,10 +13,8 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath =
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts);
const genStaticPath = (staticDir) => (...parts) =>
path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) {
const staticPath = genStaticPath(staticDir);
@@ -63,12 +62,9 @@ function copyLoaderJS(staticDir) {
function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir);
// Local fonts
fs.copySync(
npmPath("roboto-fontface/fonts/roboto/"),
staticPath("fonts/roboto/"),
{
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
}
cpx.copySync(
npmPath("roboto-fontface/fonts/roboto/*.woff2"),
staticPath("fonts/roboto")
);
}

View File

@@ -1,5 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const crypto = require("crypto");
const del = require("del");
const path = require("path");
@@ -28,6 +26,13 @@ gulp.task("translations-enable-merge-backend", (done) => {
done();
});
String.prototype.rsplit = function (sep, maxsplit) {
var split = this.split(sep);
return maxsplit
? [split.slice(0, -maxsplit).join(sep)].concat(split.slice(-maxsplit))
: split;
};
// Panel translations which should be split from the core translations.
const TRANSLATION_FRAGMENTS = Object.keys(
require("../../src/translations/en.json").ui.panel
@@ -35,7 +40,7 @@ const TRANSLATION_FRAGMENTS = Object.keys(
function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach((key) => {
Object.keys(data).forEach(function (key) {
if (typeof data[key] === "object") {
output = {
...output,
@@ -96,19 +101,15 @@ function lokaliseTransform(data, original, file) {
if (value instanceof Object) {
output[key] = lokaliseTransform(value, original, file);
} else {
output[key] = value.replace(re_key_reference, (_match, lokalise_key) => {
const replace = lokalise_key.split("::").reduce((tr, k) => {
output[key] = value.replace(re_key_reference, (match, key) => {
const replace = key.split("::").reduce((tr, k) => {
if (!tr) {
throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
}
return tr[k];
}, original);
if (typeof replace !== "string") {
throw Error(
`Invalid key placeholder ${lokalise_key} in ${file.path}`
);
throw Error(`Invalid key placeholder ${key} in ${file.path}`);
}
return replace;
});
@@ -117,7 +118,9 @@ function lokaliseTransform(data, original, file) {
return output;
}
gulp.task("clean-translations", () => del([workDir]));
gulp.task("clean-translations", function () {
return del([workDir]);
});
gulp.task("ensure-translations-build-dir", (done) => {
if (!fs.existsSync(workDir)) {
@@ -126,7 +129,7 @@ gulp.task("ensure-translations-build-dir", (done) => {
done();
});
gulp.task("create-test-metadata", (cb) => {
gulp.task("create-test-metadata", function (cb) {
fs.writeFile(
workDir + "/testMetadata.json",
JSON.stringify({
@@ -140,13 +143,17 @@ gulp.task("create-test-metadata", (cb) => {
gulp.task(
"create-test-translation",
gulp.series("create-test-metadata", () =>
gulp
gulp.series("create-test-metadata", function createTestTranslation() {
return gulp
.src(path.join(paths.translations_src, "en.json"))
.pipe(transform((data, _file) => recursiveEmpty(data)))
.pipe(
transform(function (data, file) {
return recursiveEmpty(data);
})
)
.pipe(rename("test.json"))
.pipe(gulp.dest(workDir))
)
.pipe(gulp.dest(workDir));
})
);
/**
@@ -158,7 +165,7 @@ gulp.task(
* project is buildable immediately after merging new translation keys, since
* the Lokalise update to translations/en.json will not happen immediately.
*/
gulp.task("build-master-translation", () => {
gulp.task("build-master-translation", function () {
const src = [path.join(paths.translations_src, "en.json")];
if (mergeBackend) {
@@ -167,7 +174,11 @@ gulp.task("build-master-translation", () => {
return gulp
.src(src)
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
merge({
fileName: "translationMaster.json",
@@ -176,14 +187,18 @@ gulp.task("build-master-translation", () => {
.pipe(gulp.dest(workDir));
});
gulp.task("build-merged-translations", () =>
gulp
gulp.task("build-merged-translations", function () {
return gulp
.src([inFrontendDir + "/*.json", workDir + "/test.json"], {
allowEmpty: true,
})
.pipe(transform((data, file) => lokaliseTransform(data, data, file)))
.pipe(
foreach((stream, file) => {
transform(function (data, file) {
return lokaliseTransform(data, data, file);
})
)
.pipe(
foreach(function (stream, file) {
// For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
@@ -215,17 +230,17 @@ gulp.task("build-merged-translations", () =>
)
.pipe(gulp.dest(fullDir));
})
)
);
);
});
let taskName;
var taskName;
const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = "build-translation-fragment-" + fragment;
gulp.task(taskName, () =>
gulp.task(taskName, function () {
// Return only the translations for this fragment.
gulp
return gulp
.src(fullDir + "/*.json")
.pipe(
transform((data) => ({
@@ -236,18 +251,18 @@ TRANSLATION_FRAGMENTS.forEach((fragment) => {
},
}))
)
.pipe(gulp.dest(workDir + "/" + fragment))
);
.pipe(gulp.dest(workDir + "/" + fragment));
});
splitTasks.push(taskName);
});
taskName = "build-translation-core";
gulp.task(taskName, () =>
gulp.task(taskName, function () {
// Remove the fragment translations from the core translation.
gulp
return gulp
.src(fullDir + "/*.json")
.pipe(
transform((data, _file) => {
transform((data, file) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
@@ -255,14 +270,14 @@ gulp.task(taskName, () =>
return data;
})
)
.pipe(gulp.dest(coreDir))
);
.pipe(gulp.dest(coreDir));
});
splitTasks.push(taskName);
gulp.task("build-flattened-translations", () =>
gulp.task("build-flattened-translations", function () {
// Flatten the split versions of our translations, and move them into outDir
gulp
return gulp
.src(
TRANSLATION_FRAGMENTS.map(
(fragment) => workDir + "/" + fragment + "/*.json"
@@ -270,45 +285,41 @@ gulp.task("build-flattened-translations", () =>
{ base: workDir }
)
.pipe(
transform((data) =>
transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json
flatten(data)
)
return flatten(data);
})
)
.pipe(
rename((filePath) => {
if (filePath.dirname === "core") {
filePath.dirname = "";
}
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
})
)
.pipe(gulp.dest(outDir))
);
.pipe(gulp.dest(outDir));
});
const fingerprints = {};
gulp.task("build-translation-fingerprints", () => {
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
gulp.task(
"build-translation-fingerprints",
function fingerprintTranslationFiles() {
// Fingerprint full file of each language
const files = fs.readdirSync(fullDir);
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
? crypto
.createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex")
: "dev",
};
}
for (let i = 0; i < files.length; i++) {
fingerprints[files[i].split(".")[0]] = {
// In dev we create fake hashes
hash: env.isProdBuild()
? crypto
.createHash("md5")
.update(fs.readFileSync(path.join(fullDir, files[i]), "utf-8"))
.digest("hex")
: "dev",
};
}
// In dev we create the file with the fake hash in the filename
if (env.isProdBuild()) {
mapFiles(outDir, ".json", (filename) => {
const parsed = path.parse(filename);
@@ -324,43 +335,35 @@ gulp.task("build-translation-fingerprints", () => {
}`
);
});
const stream = source("translationFingerprints.json");
stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
}
);
const stream = source("translationFingerprints.json");
stream.write(JSON.stringify(fingerprints));
process.nextTick(() => stream.end());
return stream.pipe(vinylBuffer()).pipe(gulp.dest(workDir));
});
gulp.task("build-translation-fragment-supervisor", () =>
gulp
gulp.task("build-translation-fragment-supervisor", function () {
return gulp
.src(fullDir + "/*.json")
.pipe(transform((data) => data.supervisor))
.pipe(
rename((filePath) => {
// In dev we create the file with the fake hash in the filename
if (!env.isProdBuild()) {
filePath.basename += "-dev";
}
})
)
.pipe(gulp.dest(workDir + "/supervisor"))
);
.pipe(gulp.dest(workDir + "/supervisor"));
});
gulp.task("build-translation-flatten-supervisor", () =>
gulp
gulp.task("build-translation-flatten-supervisor", function () {
return gulp
.src(workDir + "/supervisor/*.json")
.pipe(
transform((data) =>
transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json
flatten(data)
)
return flatten(data);
})
)
.pipe(gulp.dest(outDir))
);
.pipe(gulp.dest(outDir));
});
gulp.task("build-translation-write-metadata", () =>
gulp
gulp.task("build-translation-write-metadata", function writeMetadata() {
return gulp
.src(
[
path.join(paths.translations_src, "translationMetadata.json"),
@@ -371,14 +374,13 @@ gulp.task("build-translation-write-metadata", () =>
)
.pipe(merge({}))
.pipe(
transform((data) => {
transform(function (data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (value.nativeName) {
newData[key] = value;
} else {
// eslint-disable-next-line no-console
console.warn(
`Skipping language ${key}. Native name was not translated.`
);
@@ -394,26 +396,19 @@ gulp.task("build-translation-write-metadata", () =>
}))
)
.pipe(rename("translationMetadata.json"))
.pipe(gulp.dest(workDir))
);
gulp.task(
"create-translations",
gulp.series(
env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations"
)
);
.pipe(gulp.dest(workDir));
});
gulp.task(
"build-translations",
gulp.series(
"clean-translations",
"ensure-translations-build-dir",
"create-translations",
env.isProdBuild() ? (done) => done() : "create-test-translation",
"build-master-translation",
"build-merged-translations",
gulp.parallel(...splitTasks),
"build-flattened-translations",
"build-translation-fingerprints",
"build-translation-write-metadata"
)

View File

@@ -1,6 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Tasks to run webpack.
const fs = require("fs");
const gulp = require("gulp");
const webpack = require("webpack");
const WebpackDevServer = require("webpack-dev-server");
@@ -20,13 +18,6 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }),
];
const isWsl =
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase()
.includes("microsoft");
/**
* @param {{
* compiler: import("webpack").Compiler,
@@ -45,7 +36,7 @@ const runDevServer = ({
open: true,
watchContentBase: true,
contentBase,
}).listen(port, listenHost, (err) => {
}).listen(port, listenHost, function (err) {
if (err) {
throw err;
}
@@ -66,7 +57,6 @@ const doneHandler = (done) => (err, stats) => {
}
if (stats.hasErrors() || stats.hasWarnings()) {
// eslint-disable-next-line no-console
console.log(stats.toString("minimal"));
}
@@ -88,14 +78,13 @@ const prodBuild = (conf) =>
gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler
webpack(
process.env.ES5
? bothBuilds(createAppConfig, { isProdBuild: false })
: createAppConfig({ isProdBuild: false, latestBuild: true })
).watch({ poll: isWsl }, doneHandler());
webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
{ ignored: /build-translations/ },
doneHandler()
);
gulp.watch(
path.join(paths.translations_src, "en.json"),
gulp.series("create-translations", "copy-translations-app")
gulp.series("build-translations", "copy-translations-app")
);
});
@@ -148,7 +137,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false,
latestBuild: true,
})
).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
).watch({ ignored: /build-translations/ }, doneHandler());
gulp.watch(
path.join(paths.translations_src, "en.json"),

File diff suppressed because one or more lines are too long

View File

@@ -57,6 +57,7 @@ const createRollupConfig = ({
babel({
...bundle.babelOptions({ latestBuild }),
extensions,
exclude: bundle.babelExclude(),
babelHelpers: isWDS ? "inline" : "bundled",
}),
string({

View File

@@ -6,7 +6,6 @@ const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const paths = require("./paths.js");
const bundle = require("./bundle.js");
const log = require("fancy-log");
const WebpackBar = require("webpackbar");
class LogStartCompilePlugin {
ignoredFirst = false;
@@ -48,18 +47,15 @@ const createWebpackConfig = ({
rules: [
{
test: /\.m?js$|\.ts$/,
exclude: bundle.babelExclude(),
use: {
loader: "babel-loader",
options: {
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
options: bundle.babelOptions({ latestBuild }),
},
},
{
test: /\.css$/,
type: "asset/source",
use: "raw-loader",
},
],
},
@@ -71,11 +67,8 @@ const createWebpackConfig = ({
terserOptions: bundle.terserOptions(latestBuild),
}),
],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
},
plugins: [
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({
// Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"),
@@ -120,6 +113,15 @@ const createWebpackConfig = ({
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
path.resolve(paths.polymer_dir, "src/util/empty.js")
),
// We need to change the import of the polyfill for EventTarget, so we replace the polyfill file with our customized one
new webpack.NormalModuleReplacementPlugin(
new RegExp(
require.resolve(
"@lit-labs/virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
)
),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
),
!isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean),
resolve: {
@@ -127,33 +129,25 @@ const createWebpackConfig = ({
alias: {
"lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.js",
"lit/directives/until$": "lit/directives/until.js",
"lit/directives/class-map$": "lit/directives/class-map.js",
"lit/directives/style-map$": "lit/directives/style-map.js",
"lit/directives/if-defined$": "lit/directives/if-defined.js",
"lit/directives/guard$": "lit/directives/guard.js",
"lit/directives/cache$": "lit/directives/cache.js",
"lit/directives/repeat$": "lit/directives/repeat.js",
"lit/polyfill-support$": "lit/polyfill-support.js",
},
},
output: {
filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) {
if (!isProdBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`;
}
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
},
chunkFilename:
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js",
isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: outputPath,
publicPath,
// To silence warning in worker plugin
globalObject: "self",
},
experiments: {
topLevelAwait: true,
},
};
};

View File

@@ -139,7 +139,7 @@
Your authentication credentials or Home Assistant url are never sent
to the Cloud. You can validate this behavior in
<a
href="https://github.com/home-assistant/frontend/tree/dev/cast"
href="https://github.com/home-assistant/home-assistant-polymer/tree/dev/cast"
target="_blank"
>the source code</a
>.

View File

@@ -191,7 +191,7 @@ class HcCast extends LitElement {
}
this.connection.close();
location.reload();
} catch (err: any) {
} catch (err) {
alert("Unable to log out!");
}
}

View File

@@ -212,7 +212,7 @@ export class HcConnect extends LitElement {
let url: URL;
try {
url = new URL(value);
} catch (err: any) {
} catch (err) {
this.error = "Invalid URL";
return;
}
@@ -240,7 +240,7 @@ export class HcConnect extends LitElement {
try {
this.loading = true;
auth = await getAuth(options);
} catch (err: any) {
} catch (err) {
if (init === "saved-tokens" && err === ERR_CANNOT_CONNECT) {
this.cannotConnect = true;
return;
@@ -259,7 +259,7 @@ export class HcConnect extends LitElement {
try {
conn = await createConnection({ auth });
} catch (err: any) {
} catch (err) {
// In case of saved tokens, silently solve problems.
if (init === "saved-tokens") {
if (err === ERR_CANNOT_CONNECT) {
@@ -285,7 +285,7 @@ export class HcConnect extends LitElement {
try {
saveTokens(null);
location.reload();
} catch (err: any) {
} catch (err) {
alert("Unable to log out!");
}
}

View File

@@ -5,8 +5,8 @@ import {
import { castContext } from "../cast_context";
export const castDemoLovelace: () => LovelaceConfig = () => {
const touchSupported =
castContext.getDeviceCapabilities().touch_input_supported;
const touchSupported = castContext.getDeviceCapabilities()
.touch_input_supported;
return {
views: [
{

View File

@@ -148,14 +148,14 @@ export class HcMain extends HassElement {
expires_in: 0,
}),
});
} catch (err: any) {
} catch (err) {
this._error = this._getErrorMessage(err);
return;
}
let connection;
try {
connection = await createConnection({ auth });
} catch (err: any) {
} catch (err) {
this._error = this._getErrorMessage(err);
return;
}
@@ -193,7 +193,7 @@ export class HcMain extends HassElement {
this._unsubLovelace = llColl.subscribe((lovelaceConfig) =>
this._handleNewLovelaceConfig(lovelaceConfig)
);
} catch (err: any) {
} catch (err) {
// eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg);
// Generate a Lovelace config.

View File

@@ -29,11 +29,6 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
},
],
},
{
title: "Energy distribution today",
type: "energy-distribution",
link_dashboard: true,
},
{
type: "thermostat",
entity: "climate.upstairs",
@@ -118,7 +113,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -200,7 +196,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -280,7 +277,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {
@@ -317,7 +315,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png",
},
state_filter: {
on: "brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
on:
"brightness(130%) saturate(1.5) drop-shadow(0px 0px 10px gold)",
off: "brightness(80%) saturate(0.8)",
},
style: {

View File

@@ -1,6 +1,5 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
import { Lovelace } from "../../../src/panels/lovelace/types";
import { energyEntities } from "../stubs/entities";
import { DemoConfig } from "./types";
export const demoConfigs: Array<() => Promise<DemoConfig>> = [
@@ -13,8 +12,9 @@ export const demoConfigs: Array<() => Promise<DemoConfig>> = [
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfigIndex = 0;
// eslint-disable-next-line import/no-mutable-exports
export let selectedDemoConfig: Promise<DemoConfig> =
demoConfigs[selectedDemoConfigIndex]();
export let selectedDemoConfig: Promise<DemoConfig> = demoConfigs[
selectedDemoConfigIndex
]();
export const setDemoConfig = async (
hass: MockHomeAssistant,
@@ -28,7 +28,6 @@ export const setDemoConfig = async (
selectedDemoConfig = confProm;
hass.addEntities(config.entities(hass.localize), true);
hass.addEntities(energyEntities());
lovelace.saveConfig(config.lovelace(hass.localize));
hass.mockTheme(config.theme());
};

View File

@@ -980,7 +980,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-off",
custom_ui_state_card: "state-card-custom-ui",
templates: {
icon: "if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon:
"if (state === 'on') return 'mdi:account'; else if (state === 'off') return 'mdi:account-off';\n",
icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
},
@@ -1004,7 +1005,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-multiple-minus",
custom_ui_state_card: "state-card-custom-ui",
templates: {
icon: "if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon:
"if (state === 'on') return 'mdi:account-group'; else if (state === 'off') return 'mdi:account-multiple-minus';\n",
icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n",
},

View File

@@ -19,7 +19,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
@property({ attribute: false }) public hass!: MockHomeAssistant;
@state() private _switching = false;
@state() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card;
@@ -27,7 +27,12 @@ export class HADemoCard extends LitElement implements LovelaceCard {
return this._hidden ? 0 : 2;
}
public setConfig(_config: LovelaceCardConfig) {}
public setConfig(
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
config: LovelaceCardConfig
// eslint-disable-next-line @typescript-eslint/no-empty-function
) {}
protected render(): TemplateResult {
if (this._hidden) {
@@ -44,7 +49,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
(conf) => html`
${conf.name}
<small>
<a target="_blank" href=${conf.authorUrl}>
<a target="_blank" href="${conf.authorUrl}">
${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by",
"name",
@@ -94,7 +99,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
this._switching = true;
try {
await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) {
} catch (err) {
alert("Failed to switch config :-(");
} finally {
this._switching = false;

View File

@@ -1,3 +1,5 @@
import "@polymer/polymer/lib/elements/dom-if";
import "@polymer/polymer/lib/elements/dom-repeat";
import "../../src/resources/ha-style";
import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch";

View File

@@ -20,9 +20,6 @@ import { mockShoppingList } from "./stubs/shopping_list";
import { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() {
@@ -50,12 +47,8 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass);
mockMediaPlayer(hass);
mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => {

View File

@@ -1,41 +0,0 @@
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockConfig = (hass: MockHomeAssistant) => {
hass.mockAPI("config/config_entries/entry", () => [
{
entry_id: "co2signal",
domain: "co2signal",
title: "CO2 Signal",
source: "user",
state: "loaded",
supports_options: false,
supports_unload: true,
pref_disable_new_entities: false,
pref_disable_polling: false,
disabled_by: null,
reason: null,
},
]);
hass.mockWS("config/entity_registry/list", () => [
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.co2_intensity",
name: null,
icon: null,
platform: "co2signal",
},
{
config_entry_id: "co2signal",
device_id: "co2signal",
area_id: null,
disabled_by: null,
entity_id: "sensor.grid_fossil_fuel_percentage",
name: null,
icon: null,
platform: "co2signal",
},
]);
};

View File

@@ -1,134 +0,0 @@
import { format, startOfToday, startOfTomorrow } from "date-fns";
import { EnergySolarForecasts } from "../../../src/data/energy";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
export const mockEnergy = (hass: MockHomeAssistant) => {
hass.mockWS("energy/get_prefs", () => ({
energy_sources: [
{
type: "grid",
flow_from: [
{
stat_energy_from: "sensor.energy_consumption_tarif_1",
stat_cost: "sensor.energy_consumption_tarif_1_cost",
entity_energy_from: "sensor.energy_consumption_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_from: "sensor.energy_consumption_tarif_2",
stat_cost: "sensor.energy_consumption_tarif_2_cost",
entity_energy_from: "sensor.energy_consumption_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
flow_to: [
{
stat_energy_to: "sensor.energy_production_tarif_1",
stat_compensation: "sensor.energy_production_tarif_1_compensation",
entity_energy_to: "sensor.energy_production_tarif_1",
entity_energy_price: null,
number_energy_price: null,
},
{
stat_energy_to: "sensor.energy_production_tarif_2",
stat_compensation: "sensor.energy_production_tarif_2_compensation",
entity_energy_to: "sensor.energy_production_tarif_2",
entity_energy_price: null,
number_energy_price: null,
},
],
cost_adjustment_day: 0,
},
{
type: "solar",
stat_energy_from: "sensor.solar_production",
config_entry_solar_forecast: ["solar_forecast"],
},
/* {
type: "battery",
stat_energy_from: "sensor.battery_output",
stat_energy_to: "sensor.battery_input",
}, */
{
type: "gas",
stat_energy_from: "sensor.energy_gas",
stat_cost: "sensor.energy_gas_cost",
entity_energy_from: "sensor.energy_gas",
entity_energy_price: null,
number_energy_price: null,
},
],
device_consumption: [
{
stat_consumption: "sensor.energy_car",
},
{
stat_consumption: "sensor.energy_ac",
},
{
stat_consumption: "sensor.energy_washing_machine",
},
{
stat_consumption: "sensor.energy_dryer",
},
{
stat_consumption: "sensor.energy_heat_pump",
},
{
stat_consumption: "sensor.energy_boiler",
},
],
}));
hass.mockWS("energy/info", () => ({ cost_sensors: [] }));
const todayString = format(startOfToday(), "yyyy-MM-dd");
const tomorrowString = format(startOfTomorrow(), "yyyy-MM-dd");
hass.mockWS(
"energy/solar_forecast",
(): EnergySolarForecasts => ({
solar_forecast: {
wh_hours: {
[`${todayString}T06:00:00`]: 0,
[`${todayString}T06:23:00`]: 6,
[`${todayString}T06:45:00`]: 39,
[`${todayString}T07:00:00`]: 28,
[`${todayString}T08:00:00`]: 208,
[`${todayString}T09:00:00`]: 352,
[`${todayString}T10:00:00`]: 544,
[`${todayString}T11:00:00`]: 748,
[`${todayString}T12:00:00`]: 1259,
[`${todayString}T13:00:00`]: 1361,
[`${todayString}T14:00:00`]: 1373,
[`${todayString}T15:00:00`]: 1370,
[`${todayString}T16:00:00`]: 1186,
[`${todayString}T17:00:00`]: 937,
[`${todayString}T18:00:00`]: 652,
[`${todayString}T19:00:00`]: 370,
[`${todayString}T20:00:00`]: 155,
[`${todayString}T21:48:00`]: 24,
[`${todayString}T22:36:00`]: 0,
[`${tomorrowString}T06:01:00`]: 0,
[`${tomorrowString}T06:23:00`]: 9,
[`${tomorrowString}T06:45:00`]: 47,
[`${tomorrowString}T07:00:00`]: 48,
[`${tomorrowString}T08:00:00`]: 473,
[`${tomorrowString}T09:00:00`]: 827,
[`${tomorrowString}T10:00:00`]: 1153,
[`${tomorrowString}T11:00:00`]: 1413,
[`${tomorrowString}T12:00:00`]: 1590,
[`${tomorrowString}T13:00:00`]: 1652,
[`${tomorrowString}T14:00:00`]: 1612,
[`${tomorrowString}T15:00:00`]: 1438,
[`${tomorrowString}T16:00:00`]: 1149,
[`${tomorrowString}T17:00:00`]: 830,
[`${tomorrowString}T18:00:00`]: 542,
[`${tomorrowString}T19:00:00`]: 311,
[`${tomorrowString}T20:00:00`]: 140,
[`${tomorrowString}T21:47:00`]: 22,
[`${tomorrowString}T22:34:00`]: 0,
},
},
})
);
};

View File

@@ -1,178 +0,0 @@
import { convertEntities } from "../../../src/fake_data/entity";
export const energyEntities = () =>
convertEntities({
"sensor.grid_fossil_fuel_percentage": {
entity_id: "sensor.grid_fossil_fuel_percentage",
state: "88.6",
attributes: {
unit_of_measurement: "%",
},
},
"sensor.solar_production": {
entity_id: "sensor.solar_production",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Solar",
unit_of_measurement: "kWh",
},
},
"sensor.battery_input": {
entity_id: "sensor.battery_input",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Input",
unit_of_measurement: "kWh",
},
},
"sensor.battery_output": {
entity_id: "sensor.battery_output",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Battery Output",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1": {
entity_id: "sensor.energy_consumption_tarif_1 ",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_2": {
entity_id: "sensor.energy_consumption_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Grid consumption high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_1": {
entity_id: "sensor.energy_production_tarif_1",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid low tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_production_tarif_2": {
entity_id: "sensor.energy_production_tarif_2",
state: "88.6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Returned to grid high tariff",
unit_of_measurement: "kWh",
},
},
"sensor.energy_consumption_tarif_1_cost": {
entity_id: "sensor.energy_consumption_tarif_1_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_consumption_tarif_2_cost": {
entity_id: "sensor.energy_consumption_tarif_2_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_1_compensation": {
entity_id: "sensor.energy_production_tarif_1_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_production_tarif_2_compensation": {
entity_id: "sensor.energy_production_tarif_2_compensation",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas_cost": {
entity_id: "sensor.energy_gas_cost",
state: "2",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
unit_of_measurement: "EUR",
},
},
"sensor.energy_gas": {
entity_id: "sensor.energy_gas",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Gas",
unit_of_measurement: "m³",
},
},
"sensor.energy_car": {
entity_id: "sensor.energy_car",
state: "4",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Electric car",
unit_of_measurement: "kWh",
},
},
"sensor.energy_ac": {
entity_id: "sensor.energy_ac",
state: "3",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Air conditioning",
unit_of_measurement: "kWh",
},
},
"sensor.energy_washing_machine": {
entity_id: "sensor.energy_washing_machine",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Washing machine",
unit_of_measurement: "kWh",
},
},
"sensor.energy_dryer": {
entity_id: "sensor.energy_dryer",
state: "5.5",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Dryer",
unit_of_measurement: "kWh",
},
},
"sensor.energy_heat_pump": {
entity_id: "sensor.energy_heat_pump",
state: "6",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Heat pump",
unit_of_measurement: "kWh",
},
},
"sensor.energy_boiler": {
entity_id: "sensor.energy_boiler",
state: "7",
attributes: {
last_reset: "1970-01-01T00:00:00:00+00",
friendly_name: "Boiler",
unit_of_measurement: "kWh",
},
},
});

View File

@@ -1,6 +1,4 @@
import { addHours, differenceInHours, endOfDay } from "date-fns";
import { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams {
@@ -66,219 +64,17 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"];
const generateMeanStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let lastVal = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const delta = Math.random() * maxDiff;
const mean = lastVal + delta;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean,
min: mean - Math.random() * maxDiff,
max: mean + Math.random() * maxDiff,
last_reset: "1970-01-01T00:00:00+00:00",
state: mean,
sum: null,
});
lastVal = mean;
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateSumStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum,
});
currentDate = addHours(currentDate, 1);
}
return statistics;
};
const generateCurvedStatistics = (
id: string,
start: Date,
end: Date,
initValue: number,
maxDiff: number,
metered: boolean
) => {
const statistics: StatisticValue[] = [];
let currentDate = new Date(start);
currentDate.setMinutes(0, 0, 0);
let sum = initValue;
const hours = differenceInHours(end, start) - 1;
let i = 0;
let half = false;
const now = new Date();
while (end > currentDate && currentDate < now) {
const add = Math.random() * maxDiff;
sum += i * add;
statistics.push({
statistic_id: id,
start: currentDate.toISOString(),
mean: null,
min: null,
max: null,
last_reset: "1970-01-01T00:00:00+00:00",
state: initValue + sum,
sum: metered ? sum : null,
});
currentDate = addHours(currentDate, 1);
if (!half && i > hours / 2) {
half = true;
}
i += half ? -1 : 1;
}
return statistics;
};
const statisticsFunctions: Record<
string,
(id: string, start: Date, end: Date) => StatisticValue[]
> = {
"sensor.energy_consumption_tarif_1": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 10 * 60 * 60 * 1000);
const morningLow = generateSumStatistics(id, start, morningEnd, 0, 0.7);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const morningFinalVal = morningLow.length
? morningLow[morningLow.length - 1].sum!
: 0;
const empty = generateSumStatistics(
id,
morningEnd,
eveningStart,
morningFinalVal,
0
);
const eveningLow = generateSumStatistics(
id,
eveningStart,
end,
morningFinalVal,
0.7
);
return [...morningLow, ...empty, ...eveningLow];
},
"sensor.energy_consumption_tarif_2": (id: string, start: Date, end: Date) => {
const morningEnd = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const eveningStart = new Date(start.getTime() + 20 * 60 * 60 * 1000);
const highTarif = generateSumStatistics(
id,
morningEnd,
eveningStart,
0,
0.3
);
const highTarifFinalVal = highTarif.length
? highTarif[highTarif.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, morningEnd, 0, 0);
const evening = generateSumStatistics(
id,
eveningStart,
end,
highTarifFinalVal,
0
);
return [...morning, ...highTarif, ...evening];
},
"sensor.energy_production_tarif_1": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_1_compensation": (id, start, end) =>
generateSumStatistics(id, start, end, 0, 0),
"sensor.energy_production_tarif_2": (id, start, end) => {
const productionStart = new Date(start.getTime() + 9 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 21 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.15,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 1);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.solar_production": (id, start, end) => {
const productionStart = new Date(start.getTime() + 7 * 60 * 60 * 1000);
const productionEnd = new Date(start.getTime() + 23 * 60 * 60 * 1000);
const dayEnd = new Date(endOfDay(productionEnd));
const production = generateCurvedStatistics(
id,
productionStart,
productionEnd,
0,
0.3,
true
);
const productionFinalVal = production.length
? production[production.length - 1].sum!
: 0;
const morning = generateSumStatistics(id, start, productionStart, 0, 0);
const evening = generateSumStatistics(
id,
productionEnd,
dayEnd,
productionFinalVal,
0
);
const rest = generateSumStatistics(id, dayEnd, end, productionFinalVal, 2);
return [...morning, ...production, ...evening, ...rest];
},
"sensor.grid_fossil_fuel_percentage": (id, start, end) =>
generateMeanStatistics(id, start, end, 35, 1.3),
};
export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI(
new RegExp("history/period/.+"),
(hass, _method, path, _parameters) => {
(
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(",");
@@ -299,7 +95,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
const numberState = Number(state.state);
if (isNaN(numberState)) {
// eslint-disable-next-line no-console
// eslint-disable-next-line
console.log(
"Ignoring state with unparsable state but with a unit",
entityId,
@@ -344,40 +140,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results;
}
);
mockHass.mockWS("history/list_statistic_ids", () => []);
mockHass.mockWS(
"history/statistics_during_period",
({ statistic_ids, start_time, end_time }, hass) => {
const start = new Date(start_time);
const end = end_time ? new Date(end_time) : new Date();
const statistics: Record<string, StatisticValue[]> = {};
statistic_ids.forEach((id: string) => {
if (id in statisticsFunctions) {
statistics[id] = statisticsFunctions[id](id, start, end);
} else {
const entityState = hass.states[id];
const state = entityState ? Number(entityState.state) : 1;
statistics[id] =
entityState && "last_reset" in entityState.attributes
? generateSumStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.01 : 0.05)
)
: generateMeanStatistics(
id,
start,
end,
state,
state * (state > 80 ? 0.05 : 0.1)
);
}
});
return statistics;
}
);
};

View File

@@ -10,9 +10,10 @@ export const mockLovelace = (
localizePromise: Promise<LocalizeFunc>
) => {
hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then(
([config, localize]) => config.lovelace(localize)
)
Promise.all([
selectedDemoConfig,
localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
);
hass.mockWS("lovelace/config/save", () => Promise.resolve());
@@ -23,9 +24,9 @@ customElements.whenDefined("hui-view").then(() => {
// eslint-disable-next-line
const HUIView = customElements.get("hui-view");
// Patch HUI-VIEW to make the lovelace object available to the demo card
const oldCreateCard = HUIView!.prototype.createCardElement;
const oldCreateCard = HUIView.prototype.createCardElement;
HUIView!.prototype.createCardElement = function (config) {
HUIView.prototype.createCardElement = function (config) {
const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") {
(el as HADemoCard).lovelace = this.lovelace;

View File

@@ -6,7 +6,7 @@ export const mockTemplate = (hass: MockHomeAssistant) => {
body: { message: "Template dev tool does not work in the demo." },
})
);
hass.mockWS("render_template", (msg, _hass, onChange) => {
hass.mockWS("render_template", (msg, onChange) => {
onChange!({
result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false },

View File

@@ -1,4 +1,3 @@
/* eslint-disable lit/no-template-arrow */
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-card";

View File

@@ -1,14 +1,13 @@
/* eslint-disable lit/no-template-arrow */
import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline";
import { customElement, property, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace";
import { customElement, property, state } from "lit/decorators";
const traces: DemoTrace[] = [basicTrace, motionLightTrace];

View File

@@ -1,150 +0,0 @@
import { html, css, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card";
const alerts: {
title?: string;
description: string | TemplateResult;
type: "info" | "warning" | "error" | "success";
dismissable?: boolean;
action?: string;
rtl?: boolean;
}[] = [
{
title: "Test info alert",
description: "This is a test info alert with a title and description",
type: "info",
},
{
title: "Test warning alert",
description: "This is a test warning alert with a title and description",
type: "warning",
},
{
title: "Test error alert",
description: "This is a test error alert with a title and description",
type: "error",
},
{
title: "Test warning with long string",
description:
"sensor.lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum_lorem_ipsum",
type: "warning",
},
{
title: "Test success alert",
description: "This is a test success alert with a title and description",
type: "success",
},
{
description: "This is a test info alert with description only",
type: "info",
},
{
description:
"This is a test warning alert with a rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really rally really really long description only",
type: "warning",
},
{
title: "Error with description and list",
description: html`<p>
This is a test error alert with a title, description and a list
</p>
<ul>
<li>List item #1</li>
<li>List item #2</li>
<li>List item #3</li>
</ul>`,
type: "error",
},
{
title: "Test dismissable alert",
description: "This is a test success alert that can be dismissable",
type: "success",
dismissable: true,
},
{
description: "Dismissable information",
type: "info",
dismissable: true,
},
{
title: "Error with action",
description: "This is a test error alert with action",
type: "error",
action: "restart",
},
{
title: "Unsaved data",
description: "You have unsaved data",
type: "warning",
action: "save",
},
{
description: "Dismissable information (RTL)",
type: "info",
dismissable: true,
rtl: true,
},
{
title: "Error with action",
description: "This is a test error alert with action (RTL)",
type: "error",
action: "restart",
rtl: true,
},
{
title: "Test success alert (RTL)",
description: "This is a test success alert with a title and description",
type: "success",
rtl: true,
},
];
@customElement("demo-ha-alert")
export class DemoHaAlert extends LitElement {
protected render(): TemplateResult {
return html`
<ha-card header="ha-alert demo">
${alerts.map(
(alert) => html`
<ha-alert
.title=${alert.title || ""}
.alertType=${alert.type}
.dismissable=${alert.dismissable || false}
.actionText=${alert.action || ""}
.rtl=${alert.rtl || false}
>
${alert.description}
</ha-alert>
`
)}
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
.condition {
padding: 16px;
display: flex;
align-items: center;
justify-content: space-between;
}
span {
margin-right: 16px;
}
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-alert": DemoHaAlert;
}
}

View File

@@ -2,8 +2,6 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
import { IntegrationManifest } from "../../../src/data/integration";
import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -17,6 +15,8 @@ import type {
} from "../../../src/panels/config/integrations/ha-config-integrations";
import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import { EntityRegistryEntry } from "../../../src/data/entity_registry";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
const createConfigEntry = (
title: string,
@@ -28,11 +28,10 @@ const createConfigEntry = (
title,
source: "zeroconf",
state: "loaded",
connection_class: "local_push",
supports_options: false,
supports_unload: true,
disabled_by: null,
pref_disable_new_entities: false,
pref_disable_polling: false,
reason: null,
...override,
});
@@ -65,9 +64,6 @@ const configPanelEntry = createConfigEntry("Config Panel", {
const optionsFlowEntry = createConfigEntry("Options Flow", {
supports_options: true,
});
const disabledPollingEntry = createConfigEntry("Disabled Polling", {
pref_disable_polling: true,
});
const setupErrorEntry = createConfigEntry("Setup Error", {
state: "setup_error",
});
@@ -140,7 +136,6 @@ const configEntries: Array<{
{ items: [loadedEntry] },
{ items: [configPanelEntry] },
{ items: [optionsFlowEntry] },
{ items: [disabledPollingEntry] },
{ items: [nameAsDomainEntry] },
{ items: [longNameEntry] },
{ items: [longNonBreakingNameEntry] },

View File

@@ -1,5 +1,5 @@
import "@material/mwc-button";
import { css, html, LitElement, TemplateResult } from "lit";
import { html, LitElement, TemplateResult } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-card";
import { ActionHandlerEvent } from "../../../src/data/lovelace";
@@ -9,6 +9,7 @@ import { actionHandler } from "../../../src/panels/lovelace/common/directives/ac
export class DemoUtilLongPress extends LitElement {
protected render(): TemplateResult {
return html`
${this.renderStyle()}
${[1, 2, 3].map(
() => html`
<ha-card>
@@ -40,22 +41,26 @@ export class DemoUtilLongPress extends LitElement {
area.scrollTop = area.scrollHeight;
}
static styles = css`
ha-card {
width: 200px;
margin: calc(42vh - 140px) auto;
padding: 8px;
text-align: center;
}
ha-card:first-of-type {
margin-top: 16px;
}
ha-card:last-of-type {
margin-bottom: 16px;
}
private renderStyle() {
return html`
<style>
ha-card {
width: 200px;
margin: calc(42vh - 140px) auto;
padding: 8px;
text-align: center;
}
ha-card:first-of-type {
margin-top: 16px;
}
ha-card:last-of-type {
margin-bottom: 16px;
}
textarea {
height: 50px;
}
`;
textarea {
height: 50px;
}
</style>
`;
}
}

View File

@@ -172,14 +172,6 @@ class HaGallery extends PolymerElement {
this.$.notifications.showDialog({ message: ev.detail.message })
);
this.addEventListener("alert-dismissed-clicked", () =>
this.$.notifications.showDialog({ message: "Alert dismissed clicked" })
);
this.addEventListener("alert-action-clicked", () =>
this.$.notifications.showDialog({ message: "Alert action clicked" })
);
this.addEventListener("hass-more-info", (ev) => {
if (ev.detail.entityId) {
this.$.notifications.showDialog({

View File

@@ -13,7 +13,6 @@ import {
import { customElement, property, state } from "lit/decorators";
import "web-animations-js/web-animations-next-lite.min";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
@@ -54,9 +53,7 @@ class HassioAddonAudio extends LitElement {
.header=${this.supervisor.localize("addon.configuration.audio.header")}
>
<div class="card-content">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<paper-dropdown-menu
.label=${this.supervisor.localize(
@@ -120,6 +117,10 @@ class HassioAddonAudio extends LitElement {
paper-dropdown-menu {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
paper-item {
width: 450px;
}

View File

@@ -2,6 +2,7 @@ import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import "@polymer/iron-autogrow-textarea/iron-autogrow-textarea";
import { DEFAULT_SCHEMA, Type } from "js-yaml";
import {
css,
@@ -17,7 +18,6 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-form/ha-form";
import type { HaFormSchema } from "../../../../src/components/ha-form/ha-form";
import "../../../../src/components/ha-formfield";
@@ -134,21 +134,19 @@ class HassioAddonConfig extends LitElement {
></ha-form>`
: html` <ha-yaml-editor
@value-changed=${this._configChanged}
.yamlSchema=${ADDON_YAML_SCHEMA}
.schema=${ADDON_YAML_SCHEMA}
></ha-yaml-editor>`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${!this._yamlMode ||
(this._canShowSchema && this.addon.schema) ||
this._valid
? ""
: html`
<ha-alert alert-type="error">
<div class="errors">
${this.supervisor.localize(
"addon.configuration.options.invalid_yaml"
)}
</ha-alert>
</div>
`}
</div>
${hasHiddenOptions
@@ -259,7 +257,7 @@ class HassioAddonConfig extends LitElement {
path: "options",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.common.update_available",
"error",
@@ -271,9 +269,6 @@ class HassioAddonConfig extends LitElement {
private async _saveTapped(ev: CustomEvent): Promise<void> {
const button = ev.currentTarget as any;
const options: Record<string, unknown> = this._yamlMode
? this._editor?.value
: this._options;
const eventdata = {
success: true,
response: undefined,
@@ -287,20 +282,20 @@ class HassioAddonConfig extends LitElement {
const validation = await validateHassioAddonOption(
this.hass,
this.addon.slug,
options
this._editor?.value
);
if (!validation.valid) {
throw Error(validation.message);
}
await setHassioAddonOption(this.hass, this.addon.slug, {
options,
options: this._yamlMode ? this._editor?.value : this._options,
});
this._configHasChanged = false;
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
}
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -327,7 +322,17 @@ class HassioAddonConfig extends LitElement {
display: flex;
justify-content: space-between;
}
.errors {
color: var(--error-color);
margin-top: 16px;
}
iron-autogrow-textarea {
width: 100%;
font-family: var(--code-font-family, monospace);
}
.syntaxerror {
color: var(--error-color);
}
.card-menu {
float: right;
z-index: 3;

View File

@@ -10,7 +10,6 @@ import {
import { customElement, property, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import {
HassioAddonDetails,
@@ -63,9 +62,7 @@ class HassioAddonNetwork extends LitElement {
)}
>
<div class="card-content">
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<table>
<tbody>
@@ -89,9 +86,9 @@ class HassioAddonNetwork extends LitElement {
<td>
<paper-input
@value-changed=${this._configChanged}
placeholder=${this.supervisor.localize(
placeholder="${this.supervisor.localize(
"addon.configuration.network.disabled"
)}
)}"
.value=${item.host ? String(item.host) : ""}
.container=${item.container}
no-label-float
@@ -171,7 +168,7 @@ class HassioAddonNetwork extends LitElement {
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
}
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_reset",
"error",
@@ -207,7 +204,7 @@ class HassioAddonNetwork extends LitElement {
if (this.addon?.state === "started") {
await suggestAddonRestart(this, this.hass, this.supervisor, this.addon);
}
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -228,6 +225,10 @@ class HassioAddonNetwork extends LitElement {
ha-card {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
.card-actions {
display: flex;
justify-content: space-between;

View File

@@ -1,9 +1,7 @@
import "../../../../src/components/ha-card";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import { customElement, property, state } from "lit/decorators";
import {
fetchHassioAddonDocumentation,
HassioAddonDetails,
@@ -14,6 +12,7 @@ import { haStyle } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { hassioStyle } from "../../resources/hassio-style";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
@customElement("hassio-addon-documentation-tab")
class HassioAddonDocumentationDashboard extends LitElement {
@@ -39,9 +38,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
return html`
<div class="content">
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<div class="card-content">
${this._content
? html`<ha-markdown .content=${this._content}></ha-markdown>`
@@ -79,7 +76,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
this.hass,
this.addon!.slug
);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.documentation.get_logs",
"error",

View File

@@ -222,7 +222,7 @@ class HassioAddonDashboard extends LitElement {
try {
const addoninfo = await fetchHassioAddonInfo(this.hass, addon);
this.addon = addoninfo;
} catch (err: any) {
} catch (err) {
this._error = `Error fetching addon info: ${extractApiErrorMessage(err)}`;
this.addon = undefined;
}

View File

@@ -23,7 +23,6 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown";
@@ -123,18 +122,18 @@ class HassioAddonInfo extends LitElement {
<div class="card-content">
<hassio-card-content
.hass=${this.hass}
.title=${this.supervisor.localize(
.title="${this.supervisor.localize(
"addon.dashboard.new_update_available",
"name",
this.addon.name,
"version",
this.addon.version_latest
)}
.description=${this.supervisor.localize(
)}"
.description="${this.supervisor.localize(
"common.running_version",
"version",
this.addon.version
)}
)}"
icon=${mdiArrowUpBoldCircle}
iconClass="update"
></hassio-card-content>
@@ -144,14 +143,14 @@ class HassioAddonInfo extends LitElement {
this.addon.arch
)
? html`
<ha-alert alert-type="warning">
<p class="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch"
)}
</ha-alert>
</p>
`
: html`
<ha-alert alert-type="warning">
<p class="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch",
"core_version_installed",
@@ -159,7 +158,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed",
addonStoreInfo.homeassistant
)}
</ha-alert>
</p>
`
: ""}
</div>
@@ -254,7 +253,7 @@ class HassioAddonInfo extends LitElement {
${this.supervisor.localize(
"addon.dashboard.visit_addon_page",
"name",
html`<a href=${this.addon.url!} target="_blank" rel="noreferrer"
html`<a href="${this.addon.url!}" target="_blank" rel="noreferrer"
>${this.addon.name}</a
>`
)}
@@ -437,10 +436,10 @@ class HassioAddonInfo extends LitElement {
${this.addon.version
? html`
<div
class=${classMap({
class="${classMap({
"addon-options": true,
started: this.addon.state === "started",
})}
})}"
>
<ha-settings-row ?three-line=${this.narrow}>
<span slot="heading">
@@ -570,23 +569,21 @@ class HassioAddonInfo extends LitElement {
: ""}
</div>
</div>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${!this.addon.version && addonStoreInfo && !this.addon.available
? !addonArchIsSupported(
this.supervisor.info.supported_arch,
this.addon.arch
)
? html`
<ha-alert alert-type="warning">
<p class="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_arch"
)}
</ha-alert>
</p>
`
: html`
<ha-alert alert-type="warning">
<p class="warning">
${this.supervisor.localize(
"addon.dashboard.not_available_version",
"core_version_installed",
@@ -594,7 +591,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed",
addonStoreInfo!.homeassistant
)}
</ha-alert>
</p>
`
: ""}
</div>
@@ -796,7 +793,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -818,7 +815,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -840,7 +837,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -862,7 +859,7 @@ class HassioAddonInfo extends LitElement {
path: "security",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -884,7 +881,7 @@ class HassioAddonInfo extends LitElement {
path: "option",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.failed_to_save",
"error",
@@ -895,24 +892,15 @@ class HassioAddonInfo extends LitElement {
private async _openChangelog(): Promise<void> {
try {
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug);
if (
content.includes(`# ${this.addon.version}`) &&
content.includes(`# ${this.addon.version_latest}`)
) {
const newcontent = content.split(`# ${this.addon.version}`)[0];
if (newcontent.includes(`# ${this.addon.version_latest}`)) {
// Only change the content if the new version still exist
// if the changelog does not have the newests version on top
// this will not be true, and we don't modify the content
content = newcontent;
}
}
const content = await fetchHassioAddonChangelog(
this.hass,
this.addon.slug
);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"),
content,
});
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"addon.dashboard.action_error.get_changelog"
@@ -934,7 +922,7 @@ class HassioAddonInfo extends LitElement {
path: "install",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.install"),
text: extractApiErrorMessage(err),
@@ -955,7 +943,7 @@ class HassioAddonInfo extends LitElement {
path: "stop",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.stop"),
text: extractApiErrorMessage(err),
@@ -976,7 +964,7 @@ class HassioAddonInfo extends LitElement {
path: "stop",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.restart"),
text: extractApiErrorMessage(err),
@@ -990,7 +978,7 @@ class HassioAddonInfo extends LitElement {
supervisor: this.supervisor,
name: this.addon.name,
version: this.addon.version_latest,
backupParams: {
snapshotParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug],
homeassistant: false,
@@ -1035,7 +1023,7 @@ class HassioAddonInfo extends LitElement {
button.progress = false;
return;
}
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: "Failed to validate addon configuration",
text: extractApiErrorMessage(err),
@@ -1053,7 +1041,7 @@ class HassioAddonInfo extends LitElement {
path: "start",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.start"),
text: extractApiErrorMessage(err),
@@ -1091,7 +1079,7 @@ class HassioAddonInfo extends LitElement {
path: "uninstall",
};
fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"addon.dashboard.action_error.uninstall"
@@ -1152,7 +1140,6 @@ class HassioAddonInfo extends LitElement {
margin-bottom: 16px;
}
img.logo {
max-width: 100%;
max-height: 60px;
margin: 16px 0;
display: block;
@@ -1162,10 +1149,10 @@ class HassioAddonInfo extends LitElement {
display: flex;
}
ha-svg-icon.running {
color: var(--success-color);
color: var(--paper-green-400);
}
ha-svg-icon.stopped {
color: var(--error-color);
color: var(--google-red-300);
}
ha-call-api-button {
font-weight: 500;

View File

@@ -1,7 +1,6 @@
import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card";
import {
fetchHassioAddonLogs,
@@ -35,9 +34,7 @@ class HassioAddonLogs extends LitElement {
return html`
<h1>${this.addon.name}</h1>
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
<div class="card-content">
${this._content
? html`<hassio-ansi-to-html
@@ -63,6 +60,10 @@ class HassioAddonLogs extends LitElement {
ha-card {
display: block;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
`,
];
}
@@ -71,7 +72,7 @@ class HassioAddonLogs extends LitElement {
this._error = undefined;
try {
this._content = await fetchHassioAddonLogs(this.hass, this.addon.slug);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"addon.logs.get_logs",
"error",

View File

@@ -41,16 +41,16 @@ class HassioAnsiToHtml extends LitElement {
text-decoration: underline line-through;
}
.fg-red {
color: var(--error-color);
color: rgb(222, 56, 43);
}
.fg-green {
color: var(--success-color);
color: rgb(57, 181, 74);
}
.fg-yellow {
color: var(--warning-color);
color: rgb(255, 199, 6);
}
.fg-blue {
color: var(--info-color);
color: rgb(0, 111, 184);
}
.fg-magenta {
color: rgb(118, 38, 113);
@@ -65,16 +65,16 @@ class HassioAnsiToHtml extends LitElement {
background-color: rgb(0, 0, 0);
}
.bg-red {
background-color: var(--error-color);
background-color: rgb(222, 56, 43);
}
.bg-green {
background-color: var(--success-color);
background-color: rgb(57, 181, 74);
}
.bg-yellow {
background-color: var(--warning-color);
background-color: rgb(255, 199, 6);
}
.bg-blue {
background-color: var(--info-color);
background-color: rgb(0, 111, 184);
}
.bg-magenta {
background-color: rgb(118, 38, 113);

View File

@@ -37,7 +37,7 @@ class HassioCardContent extends LitElement {
${this.iconImage
? html`
<div class="icon_image ${this.iconClass}">
<img src=${this.iconImage} .title=${this.iconTitle} />
<img src="${this.iconImage}" .title=${this.iconTitle} />
<div></div>
</div>
`
@@ -80,14 +80,14 @@ class HassioCardContent extends LitElement {
color: var(--secondary-text-color);
}
ha-svg-icon.update {
color: var(--warning-color);
color: var(--paper-orange-400);
}
ha-svg-icon.running,
ha-svg-icon.installed {
color: var(--success-color);
color: var(--paper-green-400);
}
ha-svg-icon.hassupdate,
ha-svg-icon.backup {
ha-svg-icon.snapshot {
color: var(--paper-item-icon-color);
}
ha-svg-icon.not_available {
@@ -122,7 +122,7 @@ class HassioCardContent extends LitElement {
}
.dot {
position: absolute;
background-color: var(--warning-color);
background-color: var(--paper-orange-400);
width: 12px;
height: 12px;
top: 8px;

View File

@@ -1,5 +1,6 @@
import "@material/mwc-icon-button/mwc-icon-button";
import { mdiFolderUpload } from "@mdi/js";
import "@polymer/iron-input/iron-input";
import "@polymer/paper-input/paper-input-container";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
@@ -8,20 +9,23 @@ import "../../../src/components/ha-circular-progress";
import "../../../src/components/ha-file-upload";
import "../../../src/components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { HassioBackup, uploadBackup } from "../../../src/data/hassio/backup";
import {
HassioSnapshot,
uploadSnapshot,
} from "../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box";
import { HomeAssistant } from "../../../src/types";
declare global {
interface HASSDomEvents {
"backup-uploaded": { backup: HassioBackup };
"snapshot-uploaded": { snapshot: HassioSnapshot };
}
}
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
@customElement("hassio-upload-backup")
export class HassioUploadBackup extends LitElement {
@customElement("hassio-upload-snapshot")
export class HassioUploadSnapshot extends LitElement {
public hass!: HomeAssistant;
@state() public value: string | null = null;
@@ -34,7 +38,7 @@ export class HassioUploadBackup extends LitElement {
.uploading=${this._uploading}
.icon=${mdiFolderUpload}
accept="application/x-tar"
label="Upload backup"
label="Upload snapshot"
@file-picked=${this._uploadFile}
auto-open-file-dialog
></ha-file-upload>
@@ -46,10 +50,10 @@ export class HassioUploadBackup extends LitElement {
if (file.size > MAX_FILE_SIZE) {
showAlertDialog(this, {
title: "Backup file is too big",
title: "Snapshot file is too big",
text: html`The maximum allowed filesize is 1GB.<br />
<a
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-backup-on-a-new-install"
href="https://www.home-assistant.io/hassio/haos_common_tasks/#restoring-a-snapshot-on-a-new-install"
target="_blank"
>Have a look here on how to restore it.</a
>`,
@@ -61,16 +65,16 @@ export class HassioUploadBackup extends LitElement {
if (!["application/x-tar"].includes(file.type)) {
showAlertDialog(this, {
title: "Unsupported file format",
text: "Please choose a Home Assistant backup file (.tar)",
text: "Please choose a Home Assistant snapshot file (.tar)",
confirmText: "ok",
});
return;
}
this._uploading = true;
try {
const backup = await uploadBackup(this.hass, file);
fireEvent(this, "backup-uploaded", { backup: backup.data });
} catch (err: any) {
const snapshot = await uploadSnapshot(this.hass, file);
fireEvent(this, "snapshot-uploaded", { snapshot: snapshot.data });
} catch (err) {
showAlertDialog(this, {
title: "Upload failed",
text: extractApiErrorMessage(err),
@@ -84,6 +88,6 @@ export class HassioUploadBackup extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-upload-backup": HassioUploadBackup;
"hassio-upload-snapshot": HassioUploadSnapshot;
}
}

View File

@@ -29,6 +29,7 @@ class SupervisorFormfieldLabel extends LitElement {
static get styles(): CSSResultGroup {
return css`
:host {
cursor: pointer;
display: flex;
align-items: center;
}

View File

@@ -20,10 +20,10 @@ class SupervisorMetric extends LitElement {
<div slot="description" .title=${this.tooltip ?? ""}>
<span class="value"> ${roundedValue} % </span>
<ha-bar
class=${classMap({
class="${classMap({
"target-warning": roundedValue > 50,
"target-critical": roundedValue > 85,
})}
})}"
.value=${this.value}
></ha-bar>
</div>

View File

@@ -5,16 +5,15 @@ import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { formatDate } from "../../../src/common/datetime/format_date";
import { formatDateTime } from "../../../src/common/datetime/format_date_time";
import { LocalizeFunc } from "../../../src/common/translations/localize";
import "../../../src/components/ha-checkbox";
import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio";
import {
HassioFullBackupCreateParams,
HassioPartialBackupCreateParams,
HassioBackupDetail,
} from "../../../src/data/hassio/backup";
HassioFullSnapshotCreateParams,
HassioPartialSnapshotCreateParams,
HassioSnapshotDetail,
} from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types";
@@ -45,9 +44,6 @@ const _computeFolders = (folders): CheckboxItem[] => {
if (folders.includes("share")) {
list.push({ slug: "share", name: "Share", checked: false });
}
if (folders.includes("media")) {
list.push({ slug: "media", name: "Media", checked: false });
}
if (folders.includes("addons/local")) {
list.push({ slug: "addons/local", name: "Local add-ons", checked: false });
}
@@ -64,17 +60,15 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
}))
.sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-backup-content")
export class SupervisorBackupContent extends LitElement {
@customElement("supervisor-snapshot-content")
export class SupervisorSnapshotContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor;
@property({ attribute: false }) public backup?: HassioBackupDetail;
@property({ attribute: false }) public snapshot?: HassioSnapshotDetail;
@property() public backupType: HassioBackupDetail["type"] = "full";
@property() public snapshotType: HassioSnapshotDetail["type"] = "full";
@property({ attribute: false }) public folders?: CheckboxItem[];
@@ -82,116 +76,118 @@ export class SupervisorBackupContent extends LitElement {
@property({ type: Boolean }) public homeAssistant = false;
@property({ type: Boolean }) public backupHasPassword = false;
@property({ type: Boolean }) public snapshotHasPassword = false;
@property({ type: Boolean }) public onboarding = false;
@property() public snapshotName = "";
@property() public backupName = "";
@property() public backupPassword = "";
@property() public confirmBackupPassword = "";
@property() public snapshotPassword = "";
public willUpdate(changedProps) {
super.willUpdate(changedProps);
if (!this.hasUpdated) {
this.folders = _computeFolders(
this.backup
? this.backup.folders
this.snapshot
? this.snapshot.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"]
);
this.addons = _computeAddons(
this.backup ? this.backup.addons : this.supervisor?.supervisor.addons
this.snapshot
? this.snapshot.addons
: this.supervisor?.supervisor.addons
);
this.backupType = this.backup?.type || "full";
this.backupName = this.backup?.name || "";
this.backupHasPassword = this.backup?.protected || false;
this.snapshotType = this.snapshot?.type || "full";
this.snapshotName = this.snapshot?.name || "";
this.snapshotHasPassword = this.snapshot?.protected || false;
}
}
private _localize = (string: string) =>
this.supervisor?.localize(`backup.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult {
if (!this.onboarding && !this.supervisor) {
if (!this.supervisor) {
return html``;
}
const foldersSection =
this.backupType === "partial" ? this._getSection("folders") : undefined;
this.snapshotType === "partial" ? this._getSection("folders") : undefined;
const addonsSection =
this.backupType === "partial" ? this._getSection("addons") : undefined;
this.snapshotType === "partial" ? this._getSection("addons") : undefined;
return html`
${this.backup
${this.snapshot
? html`<div class="details">
${this.backup.type === "full"
? this._localize("full_backup")
: this._localize("partial_backup")}
(${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br />
${this.hass
? formatDateTime(new Date(this.backup.date), this.hass.locale)
: this.backup.date}
${this.snapshot.type === "full"
? this.supervisor.localize("snapshot.full_snapshot")
: this.supervisor.localize("snapshot.partial_snapshot")}
(${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
${formatDateTime(new Date(this.snapshot.date), this.hass.locale)}
</div>`
: html`<paper-input
name="backupName"
.label=${this._localize("name")}
.value=${this.backupName}
name="snapshotName"
.label=${this.supervisor.localize("snapshot.name")}
.value=${this.snapshotName}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`}
${!this.backup || this.backup.type === "full"
${!this.snapshot || this.snapshot.type === "full"
? html`<div class="sub-header">
${!this.backup
? this._localize("type")
: this._localize("select_type")}
${!this.snapshot
? this.supervisor.localize("snapshot.type")
: this.supervisor.localize("snapshot.select_type")}
</div>
<div class="backup-types">
<ha-formfield .label=${this._localize("full_backup")}>
<div class="snapshot-types">
<ha-formfield
.label=${this.supervisor.localize("snapshot.full_snapshot")}
>
<ha-radio
@change=${this._handleRadioValueChanged}
value="full"
name="backupType"
.checked=${this.backupType === "full"}
name="snapshotType"
.checked=${this.snapshotType === "full"}
>
</ha-radio>
</ha-formfield>
<ha-formfield .label=${this._localize("partial_backup")}>
<ha-formfield
.label=${this.supervisor!.localize("snapshot.partial_snapshot")}
>
<ha-radio
@change=${this._handleRadioValueChanged}
value="partial"
name="backupType"
.checked=${this.backupType === "partial"}
name="snapshotType"
.checked=${this.snapshotType === "partial"}
>
</ha-radio>
</ha-formfield>
</div>`
: ""}
${this.backupType === "partial"
? html`<div class="partial-picker">
${this.backup && this.backup.homeassistant
${this.snapshot && this.snapshotType === "partial"
? html`
${this.snapshot.homeassistant
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
label="Home Assistant"
.iconPath=${mdiHomeAssistant}
.version=${this.backup.homeassistant}
.version=${this.snapshot.homeassistant}
>
</supervisor-formfield-label>`}
>
<ha-checkbox
.checked=${this.homeAssistant}
@click=${this.toggleHomeAssistant}
@click=${() => {
this.homeAssistant = !this.homeAssistant;
}}
>
</ha-checkbox>
</ha-formfield>
`
: ""}
`
: ""}
${this.snapshotType === "partial"
? html`
${foldersSection?.templates.length
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("folders")}
.label=${this.supervisor.localize("snapshot.folders")}
.iconPath=${mdiFolder}
>
</supervisor-formfield-label>`}
@@ -211,7 +207,7 @@ export class SupervisorBackupContent extends LitElement {
? html`
<ha-formfield
.label=${html`<supervisor-formfield-label
.label=${this._localize("addons")}
.label=${this.supervisor.localize("snapshot.addons")}
.iconPath=${mdiPuzzle}
>
</supervisor-formfield-label>`}
@@ -227,73 +223,51 @@ export class SupervisorBackupContent extends LitElement {
<div class="section-content">${addonsSection.templates}</div>
`
: ""}
</div> `
`
: ""}
${this.backupType === "partial" &&
(!this.backup || this.backupHasPassword)
? html`<hr />`
: ""}
${!this.backup
${!this.snapshot
? html`<ha-formfield
class="password"
.label=${this._localize("password_protection")}
.label=${this.supervisor.localize("snapshot.password_protection")}
>
<ha-checkbox
.checked=${this.backupHasPassword}
.checked=${this.snapshotHasPassword}
@change=${this._toggleHasPassword}
>
</ha-checkbox>
</ha-formfield>`
</ha-checkbox
></ha-formfield>`
: ""}
${this.backupHasPassword
${this.snapshotHasPassword
? html`
<paper-input
.label=${this._localize("password")}
.label=${this.supervisor.localize("snapshot.password")}
type="password"
name="backupPassword"
.value=${this.backupPassword}
name="snapshotPassword"
.value=${this.snapshotPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>
${!this.backup
? html` <paper-input
.label=${this._localize("confirm_password")}
type="password"
name="confirmBackupPassword"
.value=${this.confirmBackupPassword}
@value-changed=${this._handleTextValueChanged}
>
</paper-input>`
: ""}
`
: ""}
`;
}
private toggleHomeAssistant() {
this.homeAssistant = !this.homeAssistant;
}
static get styles(): CSSResultGroup {
return css`
.partial-picker ha-formfield {
ha-checkbox {
--mdc-checkbox-touch-target-size: 16px;
display: block;
margin: 4px 12px 8px 0;
}
.partial-picker ha-checkbox {
--mdc-checkbox-touch-target-size: 32px;
}
.partial-picker {
display: block;
margin: 0px -6px;
ha-formfield {
display: contents;
}
supervisor-formfield-label {
display: inline-flex;
align-items: center;
}
hr {
border-color: var(--divider-color);
border-bottom: none;
margin: 16px 0;
paper-input[type="password"] {
display: block;
margin: 4px 0 4px 16px;
}
.details {
color: var(--secondary-text-color);
@@ -301,15 +275,13 @@ export class SupervisorBackupContent extends LitElement {
.section-content {
display: flex;
flex-direction: column;
margin-left: 30px;
margin-left: 16px;
}
ha-formfield.password {
display: block;
margin: 0 -14px -16px;
.security {
margin-top: 16px;
}
.backup-types {
.snapshot-types {
display: flex;
margin-left: -13px;
}
.sub-header {
margin-top: 8px;
@@ -317,23 +289,20 @@ export class SupervisorBackupContent extends LitElement {
`;
}
public backupDetails():
| HassioPartialBackupCreateParams
| HassioFullBackupCreateParams {
public snapshotDetails():
| HassioPartialSnapshotCreateParams
| HassioFullSnapshotCreateParams {
const data: any = {};
if (!this.backup) {
data.name = this.backupName || formatDate(new Date(), this.hass.locale);
if (!this.snapshot) {
data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
}
if (this.backupHasPassword) {
data.password = this.backupPassword;
if (!this.backup) {
data.confirm_password = this.confirmBackupPassword;
}
if (this.snapshotHasPassword) {
data.password = this.snapshotPassword;
}
if (this.backupType === "full") {
if (this.snapshotType === "full") {
return data;
}
@@ -362,7 +331,7 @@ export class SupervisorBackupContent extends LitElement {
const addons =
section === "addons"
? new Map(
this.supervisor?.addon.addons.map((item) => [item.slug, item])
this.supervisor!.addon.addons.map((item) => [item.slug, item])
)
: undefined;
let checkedItems = 0;
@@ -372,7 +341,6 @@ export class SupervisorBackupContent extends LitElement {
.label=${item.name}
.iconPath=${section === "addons" ? mdiPuzzle : mdiFolder}
.imageUrl=${section === "addons" &&
!this.onboarding &&
atLeastVersion(this.hass.config.version, 0, 105) &&
addons?.get(item.slug)?.icon
? `/api/hassio/addons/${item.slug}/icon`
@@ -415,7 +383,7 @@ export class SupervisorBackupContent extends LitElement {
}
private _toggleHasPassword(): void {
this.backupHasPassword = !this.backupHasPassword;
this.snapshotHasPassword = !this.snapshotHasPassword;
}
private _toggleSection(ev): void {
@@ -445,6 +413,6 @@ export class SupervisorBackupContent extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"supervisor-backup-content": SupervisorBackupContent;
"supervisor-snapshot-content": SupervisorSnapshotContent;
}
}

View File

@@ -3,7 +3,7 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate";
import { stringCompare } from "../../../src/common/string/compare";
import { compare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles";
@@ -33,7 +33,7 @@ class HassioAddons extends LitElement {
</ha-card>
`
: this.supervisor.supervisor.addons
.sort((a, b) => stringCompare(a.name, b.name))
.sort((a, b) => compare(a.name, b.name))
.map(
(addon) => html`
<ha-card .addon=${addon} @click=${this._addonTapped}>

View File

@@ -86,7 +86,7 @@ export class HassioUpdate extends LitElement {
"hassio/supervisor/update",
`https://github.com//home-assistant/hassio/releases/tag/${this.supervisor.supervisor.version_latest}`
)}
${this.supervisor.host.features.includes("haos")
${this.supervisor.host.features.includes("hassos")
? this._renderUpdateCard(
"Operating System",
"os",
@@ -136,7 +136,7 @@ export class HassioUpdate extends LitElement {
</ha-settings-row>
</div>
<div class="card-actions">
<a href=${releaseNotesUrl} target="_blank" rel="noreferrer">
<a href="${releaseNotesUrl}" target="_blank" rel="noreferrer">
<mwc-button>
${this.supervisor.localize("common.release_notes")}
</mwc-button>
@@ -162,7 +162,7 @@ export class HassioUpdate extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
backupParams: {
snapshotParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,
@@ -206,7 +206,7 @@ export class HassioUpdate extends LitElement {
fireEvent(this, "supervisor-collection-refresh", {
collection: item.key,
});
} catch (err: any) {
} catch (err) {
// Only show an error if the status code was not expected (user behind proxy)
// or no status at all(connection terminated)
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {

View File

@@ -1,352 +0,0 @@
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiClose, mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioBackupInfo,
HassioBackupDetail,
} from "../../../../src/data/hassio/backup";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
import { atLeastVersion } from "../../../../src/common/config/version";
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
@customElement("dialog-hassio-backup")
class HassioBackupDialog
extends LitElement
implements HassDialog<HassioBackupDialogParams>
{
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string;
@state() private _backup?: HassioBackupDetail;
@state() private _dialogParams?: HassioBackupDialogParams;
@state() private _restoringBackup = false;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
public async showDialog(params: HassioBackupDialogParams) {
this._backup = await fetchHassioBackupInfo(this.hass, params.slug);
this._dialogParams = params;
this._restoringBackup = false;
}
public closeDialog() {
this._backup = undefined;
this._dialogParams = undefined;
this._restoringBackup = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams || !this._backup) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${true}
>
<div slot="heading">
<ha-header-bar>
<span slot="title">${this._backup.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
${this._restoringBackup
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.backup=${this._backup}
.onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize}
>
</supervisor-backup-content>`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<mwc-button
.disabled=${this._restoringBackup}
slot="secondaryAction"
@click=${this._restoreClicked}
>
Restore
</mwc-button>
${!this._dialogParams.onboarding
? html`<ha-button-menu
fixed
slot="primaryAction"
@action=${this._handleMenuAction}
@closed=${stopPropagation}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>Download Backup</mwc-list-item>
<mwc-list-item class="error">Delete Backup</mwc-list-item>
</ha-button-menu>`
: ""}
</ha-dialog>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-svg-icon {
color: var(--primary-text-color);
}
ha-circular-progress {
display: block;
text-align: center;
}
ha-header-bar {
--mdc-theme-on-primary: var(--primary-text-color);
--mdc-theme-primary: var(--mdc-theme-surface);
flex-shrink: 0;
display: block;
}
`,
];
}
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._downloadClicked();
break;
case 1:
this._deleteClicked();
break;
}
}
private async _restoreClicked() {
const backupDetails = this._backupContent.backupDetails();
this._restoringBackup = true;
if (this._backupContent.backupType === "full") {
await this._fullRestoreClicked(backupDetails);
} else {
await this._partialRestoreClicked(backupDetails);
}
this._restoringBackup = false;
}
private async _partialRestoreClicked(backupDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this backup?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(backupDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore backup",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this backup?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(backupDetails),
});
this.closeDialog();
}
}
private async _deleteClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this backup?",
confirmText: "delete",
dismissText: "cancel",
}))
) {
return;
}
this.hass
.callApi(
atLeastVersion(this.hass.config.version, 2021, 9) ? "DELETE" : "POST",
`hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? `backups/${this._backup!.slug}`
: `snapshots/${this._backup!.slug}/remove`
}`
)
.then(
() => {
if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete();
}
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
}
private async _downloadClicked() {
let signedPath: { path: string };
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/${
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
);
} catch (err: any) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
return;
}
if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, {
title: "Potential slow download",
text: "Downloading backups over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
confirmText: "continue",
dismissText: "cancel",
});
if (!confirm) {
return;
}
}
fileDownload(
this,
signedPath.path,
`home_assistant_backup_${slugify(this._computeName)}.tar`
);
}
private get _computeName() {
return this._backup
? this._backup.name || this._backup.slug
: "Unnamed backup";
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-backup": HassioBackupDialog;
}
}

View File

@@ -1,19 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-backup-upload";
export interface HassioBackupUploadDialogParams {
showBackup: (slug: string) => void;
reloadBackup?: () => Promise<void>;
onboarding?: boolean;
}
export const showBackupUploadDialog = (
element: HTMLElement,
dialogParams: HassioBackupUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup-upload",
dialogImport: () => import("./dialog-hassio-backup-upload"),
dialogParams,
});
};

View File

@@ -1,193 +0,0 @@
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
import "@polymer/paper-item/paper-item";
import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-markdown";
import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import {
DatadiskList,
listDatadisks,
moveDatadisk,
} from "../../../../src/data/hassio/host";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioDatatiskDialogParams } from "./show-dialog-hassio-datadisk";
const calculateMoveTime = memoizeOne((supervisor: Supervisor): number => {
const speed = supervisor.host.disk_life_time !== "" ? 30 : 10;
const moveTime = (supervisor.host.disk_used * 1000) / 60 / speed;
const rebootTime = (supervisor.host.startup_time * 4) / 60;
return Math.ceil((moveTime + rebootTime) / 10) * 10;
});
@customElement("dialog-hassio-datadisk")
class HassioDatadiskDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private dialogParams?: HassioDatatiskDialogParams;
@state() private selectedDevice?: string;
@state() private devices?: DatadiskList["devices"];
@state() private moving = false;
public showDialog(params: HassioDatatiskDialogParams) {
this.dialogParams = params;
listDatadisks(this.hass).then((data) => {
this.devices = data.devices;
});
}
public closeDialog(): void {
this.dialogParams = undefined;
this.selectedDevice = undefined;
this.devices = undefined;
this.moving = false;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this.dialogParams) {
return html``;
}
let heading: string;
let content: TemplateResult;
if (this.moving) {
heading = this.dialogParams.supervisor.localize(
"dialog.datadisk_move.moving"
);
content = html`
<ha-circular-progress alt="Moving" size="large" active>
</ha-circular-progress>
<p class="progress-text">
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.moving_desc"
)}
</p>
`;
} else {
heading = this.dialogParams.supervisor.localize(
"dialog.datadisk_move.title"
);
content = html`
${this.devices?.length
? html`
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.description",
{
current_path: this.dialogParams.supervisor.os.data_disk,
time: calculateMoveTime(this.dialogParams.supervisor),
}
)}
<br /><br />
<paper-dropdown-menu
.label=${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.select_device"
)}
@value-changed=${this._select_device}
>
<paper-listbox slot="dropdown-content">
${this.devices.map(
(device) => html`<paper-item>${device}</paper-item>`
)}
</paper-listbox>
</paper-dropdown-menu>
`
: this.devices === undefined
? this.dialogParams.supervisor.localize(
"dialog.datadisk_move.loading_devices"
)
: this.dialogParams.supervisor.localize(
"dialog.datadisk_move.no_devices"
)}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this.dialogParams.supervisor.localize(
"dialog.datadisk_move.cancel"
)}
</mwc-button>
<mwc-button
.disabled=${!this.selectedDevice}
slot="primaryAction"
@click=${this._moveDatadisk}
>
${this.dialogParams.supervisor.localize("dialog.datadisk_move.move")}
</mwc-button>
`;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
@closed=${this.closeDialog}
?hideActions=${this.moving}
.heading=${heading}
>
${content}
</ha-dialog>
`;
}
private _select_device(event) {
this.selectedDevice = event.detail.value;
}
private async _moveDatadisk() {
this.moving = true;
try {
await moveDatadisk(this.hass, this.selectedDevice!);
} catch (err: any) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
title: this.dialogParams!.supervisor.localize(
"system.host.failed_to_move"
),
text: extractApiErrorMessage(err),
});
this.closeDialog();
}
}
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
paper-dropdown-menu {
width: 100%;
}
ha-circular-progress {
display: block;
margin: 32px;
text-align: center;
}
.progress-text {
text-align: center;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-datadisk": HassioDatadiskDialog;
}
}

View File

@@ -1,17 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioDatatiskDialogParams {
supervisor: Supervisor;
}
export const showHassioDatadiskDialog = (
element: HTMLElement,
dialogParams: HassioDatatiskDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-datadisk",
dialogImport: () => import("./dialog-hassio-datadisk"),
dialogParams,
});
};

View File

@@ -1,194 +0,0 @@
import { mdiClose } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input";
import { stringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { dump } from "../../../../src/resources/js-yaml-dump";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import { HassioHardwareDialogParams } from "./show-dialog-hassio-hardware";
const _filterDevices = memoizeOne(
(showAdvanced: boolean, hardware: HassioHardwareInfo, filter: string) =>
hardware.devices
.filter(
(device) =>
(showAdvanced ||
["tty", "gpio", "input"].includes(device.subsystem)) &&
(device.by_id?.toLowerCase().includes(filter) ||
device.name.toLowerCase().includes(filter) ||
device.dev_path.toLocaleLowerCase().includes(filter) ||
JSON.stringify(device.attributes)
.toLocaleLowerCase()
.includes(filter))
)
.sort((a, b) => stringCompare(a.name, b.name))
);
@customElement("dialog-hassio-hardware")
class HassioHardwareDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioHardwareDialogParams;
@state() private _filter?: string;
public showDialog(params: HassioHardwareDialogParams) {
this._dialogParams = params;
}
public closeDialog() {
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams) {
return html``;
}
const devices = _filterDevices(
this.hass.userData?.showAdvanced || false,
this._dialogParams.hardware,
(this._filter || "").toLowerCase()
);
return html`
<ha-dialog
open
scrimClickAction
hideActions
@closed=${this.closeDialog}
.heading=${true}
>
<div class="header" slot="heading">
<h2>
${this._dialogParams.supervisor.localize("dialog.hardware.title")}
</h2>
<mwc-icon-button dialogAction="close">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
<search-input
autofocus
no-label-float
.filter=${this._filter}
@value-changed=${this._handleSearchChange}
.label=${this._dialogParams.supervisor.localize(
"dialog.hardware.search"
)}
>
</search-input>
</div>
${devices.map(
(device) =>
html`<ha-expansion-panel
.header=${device.name}
.secondary=${device.by_id || undefined}
outlined
>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.subsystem"
)}:
</span>
<span>${device.subsystem}</span>
</div>
<div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.device_path"
)}:
</span>
<code>${device.dev_path}</code>
</div>
${device.by_id
? html` <div class="device-property">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.id"
)}:
</span>
<code>${device.by_id}</code>
</div>`
: ""}
<div class="attributes">
<span>
${this._dialogParams!.supervisor.localize(
"dialog.hardware.attributes"
)}:
</span>
<pre>${dump(device.attributes, { indent: 2 })}</pre>
</div>
</ha-expansion-panel>`
)}
</ha-dialog>
`;
}
private _handleSearchChange(ev: CustomEvent) {
this._filter = ev.detail.value;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
mwc-icon-button {
position: absolute;
right: 16px;
top: 10px;
text-decoration: none;
color: var(--primary-text-color);
}
h2 {
margin: 18px 42px 0 18px;
color: var(--primary-text-color);
}
ha-expansion-panel {
margin: 4px 0;
}
pre,
code {
background-color: var(--markdown-code-background-color, none);
border-radius: 3px;
}
pre {
padding: 16px;
overflow: auto;
line-height: 1.45;
font-family: var(--code-font-family, monospace);
}
code {
font-size: 85%;
padding: 0.2em 0.4em;
}
search-input {
margin: 0 16px;
display: block;
}
.device-property {
display: flex;
justify-content: space-between;
}
.attributes {
margin-top: 12px;
}
`,
];
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-hardware": HassioHardwareDialog;
}
}

View File

@@ -1,19 +0,0 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioHardwareDialogParams {
supervisor: Supervisor;
hardware: HassioHardwareInfo;
}
export const showHassioHardwareDialog = (
element: HTMLElement,
dialogParams: HassioHardwareDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-hardware",
dialogImport: () => import("./dialog-hassio-hardware"),
dialogParams,
});
};

View File

@@ -61,6 +61,10 @@ class HassioMarkdownDialog extends LitElement {
app-toolbar [main-title] {
margin-left: 16px;
}
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog {
max-height: 100%;

View File

@@ -10,7 +10,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel";
@@ -42,8 +41,7 @@ const IP_VERSIONS = ["ipv4", "ipv6"];
@customElement("dialog-hassio-network")
export class DialogHassioNetwork
extends LitElement
implements HassDialog<HassioNetworkDialogParams>
{
implements HassDialog<HassioNetworkDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -109,7 +107,7 @@ export class DialogHassioNetwork
</mwc-icon-button>
</ha-header-bar>
${this._interfaces.length > 1
? html`<mwc-tab-bar
? html` <mwc-tab-bar
.activeIndex=${this._curTabIndex}
@MDCTabBar:activated=${this._handleTabActivated}
>${this._interfaces.map(
@@ -252,9 +250,9 @@ export class DialogHassioNetwork
`
: ""}
${this._dirty
? html`<ha-alert alert-type="warning">
? html`<div class="warning">
${this.supervisor.localize("dialog.network.warning")}
</ha-alert>`
</div>`
: ""}
</div>
<div class="buttons">
@@ -287,7 +285,7 @@ export class DialogHassioNetwork
this.hass,
this._interface.interface
);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: "Failed to scan for accesspoints",
text: extractApiErrorMessage(err),
@@ -448,7 +446,7 @@ export class DialogHassioNetwork
this._interface!.interface,
interfaceOptions
);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.network.failed_to_change"),
text: extractApiErrorMessage(err),
@@ -494,7 +492,7 @@ export class DialogHassioNetwork
}
private _handleRadioValueChangedAp(ev: CustomEvent): void {
const value = (ev.target as any).value as string as
const value = ((ev.target as any).value as string) as
| "open"
| "wep"
| "wpa-psk";

View File

@@ -161,9 +161,9 @@ class HassioRegistriesDialog extends LitElement {
public focus(): void {
this.updateComplete.then(() =>
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
);
}
@@ -190,7 +190,7 @@ class HassioRegistriesDialog extends LitElement {
await addHassioDockerRegistry(this.hass, data);
await this._loadRegistries();
this._addingRegistry = false;
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_add"),
text: extractApiErrorMessage(err),
@@ -204,7 +204,7 @@ class HassioRegistriesDialog extends LitElement {
try {
await removeHassioDockerRegistry(this.hass, entry.registry);
await this._loadRegistries();
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("dialog.registries.failed_to_remove"),
text: extractApiErrorMessage(err),

View File

@@ -9,7 +9,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
@@ -76,9 +75,7 @@ class HassioRepositoriesDialog extends LitElement {
this._dialogParams!.supervisor.localize("dialog.repositories.title")
)}
>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html`<div class="error">${this._error}</div>` : ""}
<div class="form">
${repositories.length
? repositories.map(
@@ -164,9 +161,9 @@ class HassioRepositoriesDialog extends LitElement {
public focus() {
this.updateComplete.then(() =>
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
);
}
@@ -185,7 +182,7 @@ class HassioRepositoriesDialog extends LitElement {
this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err: any) {
} catch (err) {
this._error = extractApiErrorMessage(err);
}
}
@@ -207,7 +204,7 @@ class HassioRepositoriesDialog extends LitElement {
await this._loadData();
input.value = "";
} catch (err: any) {
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._processing = false;
@@ -229,7 +226,7 @@ class HassioRepositoriesDialog extends LitElement {
addons_repositories: newRepositories,
});
await this._loadData();
} catch (err: any) {
} catch (err) {
this._error = extractApiErrorMessage(err);
}
}

View File

@@ -2,42 +2,41 @@ import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
createHassioFullBackup,
createHassioPartialBackup,
} from "../../../../src/data/hassio/backup";
createHassioFullSnapshot,
createHassioPartialSnapshot,
} from "../../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-backup-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot";
@customElement("dialog-hassio-create-backup")
class HassioCreateBackupDialog extends LitElement {
@customElement("dialog-hassio-create-snapshot")
class HassioCreateSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateBackupDialogParams;
@state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _error?: string;
@state() private _creatingBackup = false;
@state() private _creatingSnapshot = false;
@query("supervisor-backup-content")
private _backupContent!: SupervisorBackupContent;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
public showDialog(params: HassioCreateBackupDialogParams) {
public showDialog(params: HassioCreateSnapshotDialogParams) {
this._dialogParams = params;
this._creatingBackup = false;
this._creatingSnapshot = false;
}
public closeDialog() {
this._dialogParams = undefined;
this._creatingBackup = false;
this._creatingSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
@@ -53,84 +52,73 @@ class HassioCreateBackupDialog extends LitElement {
@closed=${this.closeDialog}
.heading=${createCloseHeading(
this.hass,
this._dialogParams.supervisor.localize("backup.create_backup")
this._dialogParams.supervisor.localize("snapshot.create_snapshot")
)}
>
${this._creatingBackup
${this._creatingSnapshot
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content
: html`<supervisor-snapshot-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
>
</supervisor-backup-content>`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
</supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")}
</mwc-button>
<mwc-button
.disabled=${this._creatingBackup}
.disabled=${this._creatingSnapshot}
slot="primaryAction"
@click=${this._createBackup}
@click=${this._createSnapshot}
>
${this._dialogParams.supervisor.localize("backup.create")}
${this._dialogParams.supervisor.localize("snapshot.create")}
</mwc-button>
</ha-dialog>
`;
}
private async _createBackup(): Promise<void> {
private async _createSnapshot(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize(
"backup.could_not_create"
"snapshot.could_not_create"
),
text: this._dialogParams!.supervisor.localize(
"backup.create_blocked_not_running",
"snapshot.create_blocked_not_running",
"state",
this._dialogParams!.supervisor.info.state
),
});
return;
}
const backupDetails = this._backupContent.backupDetails();
this._creatingBackup = true;
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._creatingSnapshot = true;
this._error = "";
if (backupDetails.password && !backupDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize(
"backup.enter_password"
);
this._creatingBackup = false;
return;
}
if (
backupDetails.password &&
backupDetails.password !== backupDetails.confirm_password
this._snapshotContent.snapshotHasPassword &&
!this._snapshotContent.snapshotPassword.length
) {
this._error = this._dialogParams!.supervisor.localize(
"backup.passwords_not_matching"
"snapshot.enter_password"
);
this._creatingBackup = false;
this._creatingSnapshot = false;
return;
}
delete backupDetails.confirm_password;
try {
if (this._backupContent.backupType === "full") {
await createHassioFullBackup(this.hass, backupDetails);
if (this._snapshotContent.snapshotType === "full") {
await createHassioFullSnapshot(this.hass, snapshotDetails);
} else {
await createHassioPartialBackup(this.hass, backupDetails);
await createHassioPartialSnapshot(this.hass, snapshotDetails);
}
this._dialogParams!.onCreate();
this.closeDialog();
} catch (err: any) {
} catch (err) {
this._error = extractApiErrorMessage(err);
}
this._creatingBackup = false;
this._creatingSnapshot = false;
}
static get styles(): CSSResultGroup {
@@ -149,6 +137,6 @@ class HassioCreateBackupDialog extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-create-backup": HassioCreateBackupDialog;
"dialog-hassio-create-snapshot": HassioCreateSnapshotDialog;
}
}

View File

@@ -6,20 +6,19 @@ import "../../../../src/components/ha-header-bar";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-upload-backup";
import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload";
import "../../components/hassio-upload-snapshot";
import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload";
@customElement("dialog-hassio-backup-upload")
export class DialogHassioBackupUpload
@customElement("dialog-hassio-snapshot-upload")
export class DialogHassioSnapshotUpload
extends LitElement
implements HassDialog<HassioBackupUploadDialogParams>
{
implements HassDialog<HassioSnapshotUploadDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioBackupUploadDialogParams;
@state() private _params?: HassioSnapshotUploadDialogParams;
public async showDialog(
params: HassioBackupUploadDialogParams
params: HassioSnapshotUploadDialogParams
): Promise<void> {
this._params = params;
await this.updateComplete;
@@ -27,8 +26,8 @@ export class DialogHassioBackupUpload
public closeDialog(): void {
if (this._params && !this._params.onboarding) {
if (this._params.reloadBackup) {
this._params.reloadBackup();
if (this._params.reloadSnapshot) {
this._params.reloadSnapshot();
}
}
this._params = undefined;
@@ -51,23 +50,23 @@ export class DialogHassioBackupUpload
>
<div slot="heading">
<ha-header-bar>
<span slot="title"> Upload backup </span>
<span slot="title"> Upload snapshot </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button>
</ha-header-bar>
</div>
<hassio-upload-backup
@backup-uploaded=${this._backupUploaded}
<hassio-upload-snapshot
@snapshot-uploaded=${this._snapshotUploaded}
.hass=${this.hass}
></hassio-upload-backup>
></hassio-upload-snapshot>
</ha-dialog>
`;
}
private _backupUploaded(ev) {
const backup = ev.detail.backup;
this._params?.showBackup(backup.slug);
private _snapshotUploaded(ev) {
const snapshot = ev.detail.snapshot;
this._params?.showSnapshot(snapshot.slug);
this.closeDialog();
}
@@ -94,6 +93,6 @@ export class DialogHassioBackupUpload
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-backup-upload": DialogHassioBackupUpload;
"dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload;
}
}

View File

@@ -0,0 +1,310 @@
import { ActionDetail } from "@material/mwc-list";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-button-menu";
import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import {
fetchHassioSnapshotInfo,
HassioSnapshotDetail,
} from "../../../../src/data/hassio/snapshot";
import {
showAlertDialog,
showConfirmationDialog,
} from "../../../../src/dialogs/generic/show-dialog-box";
import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-snapshot-content";
import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
@customElement("dialog-hassio-snapshot")
class HassioSnapshotDialog
extends LitElement
implements HassDialog<HassioSnapshotDialogParams> {
@property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string;
@state() private _snapshot?: HassioSnapshotDetail;
@state() private _dialogParams?: HassioSnapshotDialogParams;
@state() private _restoringSnapshot = false;
@query("supervisor-snapshot-content")
private _snapshotContent!: SupervisorSnapshotContent;
public async showDialog(params: HassioSnapshotDialogParams) {
this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._dialogParams = params;
this._restoringSnapshot = false;
}
public closeDialog() {
this._snapshot = undefined;
this._dialogParams = undefined;
this._restoringSnapshot = false;
this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
}
protected render(): TemplateResult {
if (!this._dialogParams || !this._snapshot) {
return html``;
}
return html`
<ha-dialog
open
scrimClickAction
@closed=${this.closeDialog}
.heading=${createCloseHeading(this.hass, this._computeName)}
>
${this._restoringSnapshot
? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-snapshot-content
.hass=${this.hass}
.supervisor=${this._dialogParams.supervisor}
.snapshot=${this._snapshot}
>
</supervisor-snapshot-content>`}
${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
<mwc-button
.disabled=${this._restoringSnapshot}
slot="secondaryAction"
@click=${this._restoreClicked}
>
Restore
</mwc-button>
<ha-button-menu
fixed
slot="primaryAction"
@action=${this._handleMenuAction}
@closed=${(ev: Event) => ev.stopPropagation()}
>
<mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item>Download Snapshot</mwc-list-item>
<mwc-list-item class="error">Delete Snapshot</mwc-list-item>
</ha-button-menu>
</ha-dialog>
`;
}
static get styles(): CSSResultGroup {
return [
haStyle,
haStyleDialog,
css`
ha-svg-icon {
color: var(--primary-text-color);
}
ha-circular-progress {
display: block;
text-align: center;
}
`,
];
}
private _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
this._downloadClicked();
break;
case 1:
this._deleteClicked();
break;
}
}
private async _restoreClicked() {
const snapshotDetails = this._snapshotContent.snapshotDetails();
this._restoringSnapshot = true;
if (this._snapshotContent.snapshotType === "full") {
await this._fullRestoreClicked(snapshotDetails);
} else {
await this._partialRestoreClicked(snapshotDetails);
}
this._restoringSnapshot = false;
}
private async _partialRestoreClicked(snapshotDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
snapshotDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
method: "POST",
body: JSON.stringify(snapshotDetails),
});
this.closeDialog();
}
}
private async _fullRestoreClicked(snapshotDetails) {
if (
this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running"
) {
await showAlertDialog(this, {
title: "Could not restore snapshot",
text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
});
return;
}
if (
!(await showConfirmationDialog(this, {
title:
"Are you sure you want to wipe your system and restore this snapshot?",
confirmText: "restore",
dismissText: "cancel",
}))
) {
return;
}
if (!this._dialogParams?.onboarding) {
this.hass
.callApi(
"POST",
`hassio/snapshots/${this._snapshot!.slug}/restore/full`,
snapshotDetails
)
.then(
() => {
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
} else {
fireEvent(this, "restoring");
fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
method: "POST",
body: JSON.stringify(snapshotDetails),
});
this.closeDialog();
}
}
private async _deleteClicked() {
if (
!(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this snapshot?",
confirmText: "delete",
dismissText: "cancel",
}))
) {
return;
}
this.hass
.callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
.then(
() => {
if (this._dialogParams!.onDelete) {
this._dialogParams!.onDelete();
}
this.closeDialog();
},
(error) => {
this._error = error.body.message;
}
);
}
private async _downloadClicked() {
let signedPath: { path: string };
try {
signedPath = await getSignedPath(
this.hass,
`/api/hassio/snapshots/${this._snapshot!.slug}/download`
);
} catch (err) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
});
return;
}
if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, {
title: "Potential slow download",
text:
"Downloading snapshots over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
confirmText: "continue",
dismissText: "cancel",
});
if (!confirm) {
return;
}
}
const a = document.createElement("a");
a.href = signedPath.path;
a.download = `home_assistant_snapshot_${slugify(this._computeName)}.tar`;
this.shadowRoot!.appendChild(a);
a.click();
this.shadowRoot!.removeChild(a);
}
private get _computeName() {
return this._snapshot
? this._snapshot.name || this._snapshot.slug
: "Unnamed snapshot";
}
}
declare global {
interface HTMLElementTagNameMap {
"dialog-hassio-snapshot": HassioSnapshotDialog;
}
}

View File

@@ -1,18 +1,18 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioCreateBackupDialogParams {
export interface HassioCreateSnapshotDialogParams {
supervisor: Supervisor;
onCreate: () => void;
}
export const showHassioCreateBackupDialog = (
export const showHassioCreateSnapshotDialog = (
element: HTMLElement,
dialogParams: HassioCreateBackupDialogParams
dialogParams: HassioCreateSnapshotDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-create-backup",
dialogImport: () => import("./dialog-hassio-create-backup"),
dialogTag: "dialog-hassio-create-snapshot",
dialogImport: () => import("./dialog-hassio-create-snapshot"),
dialogParams,
});
};

View File

@@ -1,22 +1,20 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioBackupDialogParams {
export interface HassioSnapshotDialogParams {
slug: string;
onDelete?: () => void;
onboarding?: boolean;
supervisor?: Supervisor;
localize?: LocalizeFunc;
}
export const showHassioBackupDialog = (
export const showHassioSnapshotDialog = (
element: HTMLElement,
dialogParams: HassioBackupDialogParams
dialogParams: HassioSnapshotDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup",
dialogImport: () => import("./dialog-hassio-backup"),
dialogTag: "dialog-hassio-snapshot",
dialogImport: () => import("./dialog-hassio-snapshot"),
dialogParams,
});
};

View File

@@ -0,0 +1,19 @@
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "./dialog-hassio-snapshot-upload";
export interface HassioSnapshotUploadDialogParams {
showSnapshot: (slug: string) => void;
reloadSnapshot?: () => Promise<void>;
onboarding?: boolean;
}
export const showSnapshotUploadDialog = (
element: HTMLElement,
dialogParams: HassioSnapshotUploadDialogParams
): void => {
fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-snapshot-upload",
dialogImport: () => import("./dialog-hassio-snapshot-upload"),
dialogParams,
});
};

View File

@@ -26,7 +26,7 @@ export const suggestAddonRestart = async (
if (confirmed) {
try {
await restartHassioAddon(hass, addon.slug);
} catch (err: any) {
} catch (err) {
showAlertDialog(element, {
title: supervisor.localize(
"common.failed_to_restart_name",

View File

@@ -2,7 +2,6 @@ import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row";
@@ -12,7 +11,7 @@ import {
extractApiErrorMessage,
ignoreSupervisorError,
} from "../../../../src/data/hassio/common";
import { createHassioPartialBackup } from "../../../../src/data/hassio/backup";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
@@ -23,9 +22,9 @@ class DialogSupervisorUpdate extends LitElement {
@state() private _opened = false;
@state() private _createBackup = true;
@state() private _createSnapshot = true;
@state() private _action: "backup" | "update" | null = null;
@state() private _action: "snapshot" | "update" | null = null;
@state() private _error?: string;
@@ -42,7 +41,7 @@ class DialogSupervisorUpdate extends LitElement {
public closeDialog(): void {
this._action = null;
this._createBackup = true;
this._createSnapshot = true;
this._error = undefined;
this._dialogParams = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName });
@@ -50,9 +49,9 @@ class DialogSupervisorUpdate extends LitElement {
public focus(): void {
this.updateComplete.then(() =>
(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement
)?.focus()
(this.shadowRoot?.querySelector(
"[dialogInitialFocus]"
) as HTMLElement)?.focus()
);
}
@@ -85,20 +84,20 @@ class DialogSupervisorUpdate extends LitElement {
<ha-settings-row>
<span slot="heading">
${this._dialogParams.supervisor.localize(
"dialog.update.backup"
"dialog.update.snapshot"
)}
</span>
<span slot="description">
${this._dialogParams.supervisor.localize(
"dialog.update.create_backup",
"dialog.update.create_snapshot",
"name",
this._dialogParams.name
)}
</span>
<ha-switch
.checked=${this._createBackup}
.checked=${this._createSnapshot}
haptic
@click=${this._toggleBackup}
@click=${this._toggleSnapshot}
>
</ha-switch>
</ha-settings-row>
@@ -124,31 +123,29 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.version
)
: this._dialogParams.supervisor.localize(
"dialog.update.creating_backup",
"dialog.update.snapshotting",
"name",
this._dialogParams.name
)}
</p>`}
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html`<p class="error">${this._error}</p>` : ""}
</ha-dialog>
`;
}
private _toggleBackup() {
this._createBackup = !this._createBackup;
private _toggleSnapshot() {
this._createSnapshot = !this._createSnapshot;
}
private async _update() {
if (this._createBackup) {
this._action = "backup";
if (this._createSnapshot) {
this._action = "snapshot";
try {
await createHassioPartialBackup(
await createHassioPartialSnapshot(
this.hass,
this._dialogParams!.backupParams
this._dialogParams!.snapshotParams
);
} catch (err: any) {
} catch (err) {
this._error = extractApiErrorMessage(err);
this._action = null;
return;
@@ -158,11 +155,11 @@ class DialogSupervisorUpdate extends LitElement {
this._action = "update";
try {
await this._dialogParams!.updateHandler!();
} catch (err: any) {
} catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err);
this._action = null;
}
this._action = null;
return;
}

View File

@@ -5,7 +5,7 @@ export interface SupervisorDialogSupervisorUpdateParams {
supervisor: Supervisor;
name: string;
version: string;
backupParams: any;
snapshotParams: any;
updateHandler: () => Promise<void>;
}

View File

@@ -121,7 +121,7 @@ export class HassioMain extends SupervisorBaseElement {
}
} else {
themeName =
(this.hass.selectedTheme as unknown as string) ||
((this.hass.selectedTheme as unknown) as string) ||
this.hass.themes.default_theme;
}

View File

@@ -1,19 +1,19 @@
import { sanitizeUrl } from "@braintree/sanitize-url";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import { navigate } from "../../src/common/navigate";
import { sanitizeUrl } from "@braintree/sanitize-url";
import {
createSearchParam,
extractSearchParamsObject,
} from "../../src/common/url/search-params";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import "../../src/layouts/hass-error-screen";
import {
ParamType,
Redirect,
Redirects,
} from "../../src/panels/my/ha-panel-my";
import { navigate } from "../../src/common/navigate";
import { HomeAssistant, Route } from "../../src/types";
import { Supervisor } from "../../src/data/supervisor/supervisor";
import { customElement, property, state } from "lit/decorators";
const REDIRECTS: Redirects = {
supervisor: {
@@ -26,10 +26,7 @@ const REDIRECTS: Redirects = {
redirect: "/hassio/system",
},
supervisor_snapshots: {
redirect: "/hassio/backups",
},
supervisor_backups: {
redirect: "/hassio/backups",
redirect: "/hassio/snapshots",
},
supervisor_store: {
redirect: "/hassio/store",
@@ -87,7 +84,7 @@ class HassioMyRedirect extends LitElement {
let url: string;
try {
url = this._createRedirectUrl(redirect);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize("my.error");
return;
}

View File

@@ -9,7 +9,7 @@ import "./addon-store/hassio-addon-store";
// Don't codesplit it, that way the dashboard always loads fast.
import "./dashboard/hassio-dashboard";
// Don't codesplit the others, because it breaks the UI when pushed to a Pi
import "./backups/hassio-backups";
import "./snapshots/hassio-snapshots";
import "./system/hassio-system";
@customElement("hassio-panel-router")
@@ -23,8 +23,6 @@ class HassioPanelRouter extends HassRouterPage {
@property({ type: Boolean }) public narrow!: boolean;
protected routerOptions: RouterOptions = {
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
routes: {
dashboard: {
tag: "hassio-dashboard",
@@ -32,8 +30,8 @@ class HassioPanelRouter extends HassRouterPage {
store: {
tag: "hassio-addon-store",
},
backups: {
tag: "hassio-backups",
snapshots: {
tag: "hassio-snapshots",
},
system: {
tag: "hassio-system",

View File

@@ -23,8 +23,6 @@ class HassioRouter extends HassRouterPage {
protected routerOptions: RouterOptions = {
// Hass.io has a page with tabs, so we route all non-matching routes to it.
defaultPage: "dashboard",
beforeRender: (page: string) =>
page === "snapshots" ? "backups" : undefined,
initialLoad: () => this._redirectIngress(),
showLoading: true,
routes: {
@@ -32,7 +30,7 @@ class HassioRouter extends HassRouterPage {
tag: "hassio-panel",
cache: true,
},
backups: "dashboard",
snapshots: "dashboard",
store: "dashboard",
system: "dashboard",
addon: {

View File

@@ -13,8 +13,8 @@ export const supervisorTabs: PageNavigation[] = [
iconPath: mdiStore,
},
{
translationKey: "panel.backups",
path: `/hassio/backups`,
translationKey: "panel.snapshots",
path: `/hassio/snapshots`,
iconPath: mdiBackupRestore,
},
{

View File

@@ -91,29 +91,22 @@ class HassioIngressView extends LitElement {
if (requestedAddon) {
try {
addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon);
} catch (err: any) {
} catch (err) {
await showAlertDialog(this, {
text: extractApiErrorMessage(err),
title: requestedAddon,
});
await nextRender();
navigate("/hassio/store", { replace: true });
history.back();
return;
}
if (!addonInfo.version) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_not_installed"),
title: addonInfo.name,
});
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
} else if (!addonInfo.ingress) {
if (!addonInfo.ingress) {
await showAlertDialog(this, {
text: this.supervisor.localize("my.error_addon_no_ingress"),
title: addonInfo.name,
});
await nextRender();
navigate(`/hassio/addon/${addonInfo.slug}/info`, { replace: true });
history.back();
} else {
navigate(`/hassio/ingress/${addonInfo.slug}`, { replace: true });
}
@@ -145,7 +138,7 @@ class HassioIngressView extends LitElement {
try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err: any) {
} catch (err) {
await showAlertDialog(this, {
text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor",
@@ -179,7 +172,7 @@ class HassioIngressView extends LitElement {
try {
session = await createSessionPromise;
} catch (err: any) {
} catch (err) {
await showAlertDialog(this, {
text: "Unable to create an Ingress session",
title: addon.name,
@@ -195,7 +188,7 @@ class HassioIngressView extends LitElement {
this._sessionKeepAlive = window.setInterval(async () => {
try {
await validateHassioSession(this.hass, session);
} catch (err: any) {
} catch (err) {
session = await createHassioSession(this.hass);
}
}, 60000);

View File

@@ -14,7 +14,7 @@ import { customElement, property, query, state } from "lit/decorators";
import { classMap } from "lit/directives/class-map";
import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version";
import { relativeTime } from "../../../src/common/datetime/relative_time";
import relativeTime from "../../../src/common/datetime/relative_time";
import { HASSDomEvent } from "../../../src/common/dom/fire_event";
import {
DataTableColumnContainer,
@@ -25,12 +25,12 @@ import "../../../src/components/ha-button-menu";
import "../../../src/components/ha-fab";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import {
fetchHassioBackups,
fetchHassioSnapshots,
friendlyFolderName,
HassioBackup,
reloadHassioBackups,
removeBackup,
} from "../../../src/data/hassio/backup";
HassioSnapshot,
reloadHassioSnapshots,
removeSnapshot,
} from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor";
import {
showAlertDialog,
@@ -40,14 +40,14 @@ import "../../../src/layouts/hass-tabs-subpage-data-table";
import type { HaTabsSubpageDataTable } from "../../../src/layouts/hass-tabs-subpage-data-table";
import { haStyle } from "../../../src/resources/styles";
import { HomeAssistant, Route } from "../../../src/types";
import { showHassioCreateBackupDialog } from "../dialogs/backup/show-dialog-hassio-create-backup";
import { showHassioBackupDialog } from "../dialogs/backup/show-dialog-hassio-backup";
import { showBackupUploadDialog } from "../dialogs/backup/show-dialog-backup-upload";
import { showHassioCreateSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-create-snapshot";
import { showHassioSnapshotDialog } from "../dialogs/snapshot/show-dialog-hassio-snapshot";
import { showSnapshotUploadDialog } from "../dialogs/snapshot/show-dialog-snapshot-upload";
import { supervisorTabs } from "../hassio-tabs";
import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-backups")
export class HassioBackups extends LitElement {
@customElement("hassio-snapshots")
export class HassioSnapshots extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor;
@@ -58,9 +58,9 @@ export class HassioBackups extends LitElement {
@property({ type: Boolean }) public isWide!: boolean;
@state() private _selectedBackups: string[] = [];
@state() private _selectedSnapshots: string[] = [];
@state() private _backups?: HassioBackup[] = [];
@state() private _snapshots?: HassioSnapshot[] = [];
@query("hass-tabs-subpage-data-table", true)
private _dataTable!: HaTabsSubpageDataTable;
@@ -75,26 +75,26 @@ export class HassioBackups extends LitElement {
}
public async refreshData() {
await reloadHassioBackups(this.hass);
await this.fetchBackups();
await reloadHassioSnapshots(this.hass);
await this.fetchSnapshots();
}
private _computeBackupContent = (backup: HassioBackup): string => {
if (backup.type === "full") {
return this.supervisor.localize("backup.full_backup");
private _computeSnapshotContent = (snapshot: HassioSnapshot): string => {
if (snapshot.type === "full") {
return this.supervisor.localize("snapshot.full_snapshot");
}
const content: string[] = [];
if (backup.content.homeassistant) {
if (snapshot.content.homeassistant) {
content.push("Home Assistant");
}
if (backup.content.folders.length !== 0) {
for (const folder of backup.content.folders) {
if (snapshot.content.folders.length !== 0) {
for (const folder of snapshot.content.folders) {
content.push(friendlyFolderName[folder] || folder);
}
}
if (backup.content.addons.length !== 0) {
for (const addon of backup.content.addons) {
if (snapshot.content.addons.length !== 0) {
for (const addon of snapshot.content.addons) {
content.push(
this.supervisor.supervisor.addons.find(
(entry) => entry.slug === addon
@@ -117,23 +117,23 @@ export class HassioBackups extends LitElement {
private _columns = memoizeOne(
(narrow: boolean): DataTableColumnContainer => ({
name: {
title: this.supervisor?.localize("backup.name") || "",
title: this.supervisor?.localize("snapshot.name") || "",
sortable: true,
filterable: true,
grows: true,
template: (entry: string, backup: any) =>
html`${entry || backup.slug}
<div class="secondary">${backup.secondary}</div>`,
template: (entry: string, snapshot: any) =>
html`${entry || snapshot.slug}
<div class="secondary">${snapshot.secondary}</div>`,
},
date: {
title: this.supervisor?.localize("backup.created") || "",
title: this.supervisor?.localize("snapshot.created") || "",
width: "15%",
direction: "desc",
hidden: narrow,
filterable: true,
sortable: true,
template: (entry: string) =>
relativeTime(new Date(entry), this.hass.locale),
relativeTime(new Date(entry), this.hass.localize),
},
secondary: {
title: "",
@@ -143,10 +143,10 @@ export class HassioBackups extends LitElement {
})
);
private _backupData = memoizeOne((backups: HassioBackup[]) =>
backups.map((backup) => ({
...backup,
secondary: this._computeBackupContent(backup),
private _snapshotData = memoizeOne((snapshots: HassioSnapshot[]) =>
snapshots.map((snapshot) => ({
...snapshot,
secondary: this._computeSnapshotContent(snapshot),
}))
);
@@ -160,11 +160,11 @@ export class HassioBackups extends LitElement {
.hass=${this.hass}
.localizeFunc=${this.supervisor.localize}
.searchLabel=${this.supervisor.localize("search")}
.noDataText=${this.supervisor.localize("backup.no_backups")}
.noDataText=${this.supervisor.localize("snapshot.no_snapshots")}
.narrow=${this.narrow}
.route=${this.route}
.columns=${this._columns(this.narrow)}
.data=${this._backupData(this._backups || [])}
.data=${this._snapshotData(this._snapshots || [])}
id="slug"
@row-click=${this._handleRowClicked}
@selection-changed=${this._handleSelectionChanged}
@@ -187,12 +187,12 @@ export class HassioBackups extends LitElement {
</mwc-list-item>
${atLeastVersion(this.hass.config.version, 0, 116)
? html`<mwc-list-item>
${this.supervisor?.localize("backup.upload_backup")}
${this.supervisor?.localize("snapshot.upload_snapshot")}
</mwc-list-item>`
: ""}
</ha-button-menu>
${this._selectedBackups.length
${this._selectedSnapshots.length
? html`<div
class=${classMap({
"header-toolbar": this.narrow,
@@ -201,8 +201,8 @@ export class HassioBackups extends LitElement {
slot="header"
>
<p class="selected-txt">
${this.supervisor.localize("backup.selected", {
number: this._selectedBackups.length,
${this.supervisor.localize("snapshot.selected", {
number: this._selectedSnapshots.length,
})}
</p>
<div class="header-btns">
@@ -212,7 +212,7 @@ export class HassioBackups extends LitElement {
@click=${this._deleteSelected}
class="warning"
>
${this.supervisor.localize("backup.delete_selected")}
${this.supervisor.localize("snapshot.delete_selected")}
</mwc-button>
`
: html`
@@ -224,7 +224,7 @@ export class HassioBackups extends LitElement {
<ha-svg-icon .path=${mdiDelete}></ha-svg-icon>
</mwc-icon-button>
<paper-tooltip animation-delay="0" for="delete-btn">
${this.supervisor.localize("backup.delete_selected")}
${this.supervisor.localize("snapshot.delete_selected")}
</paper-tooltip>
`}
</div>
@@ -233,8 +233,8 @@ export class HassioBackups extends LitElement {
<ha-fab
slot="fab"
@click=${this._createBackup}
.label=${this.supervisor.localize("backup.create_backup")}
@click=${this._createSnapshot}
.label=${this.supervisor.localize("snapshot.create_snapshot")}
extended
>
<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>
@@ -249,7 +249,7 @@ export class HassioBackups extends LitElement {
this.refreshData();
break;
case 1:
this._showUploadBackupDialog();
this._showUploadSnapshotDialog();
break;
}
}
@@ -257,33 +257,33 @@ export class HassioBackups extends LitElement {
private _handleSelectionChanged(
ev: HASSDomEvent<SelectionChangedEvent>
): void {
this._selectedBackups = ev.detail.value;
this._selectedSnapshots = ev.detail.value;
}
private _showUploadBackupDialog() {
showBackupUploadDialog(this, {
showBackup: (slug: string) =>
showHassioBackupDialog(this, {
private _showUploadSnapshotDialog() {
showSnapshotUploadDialog(this, {
showSnapshot: (slug: string) =>
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchBackups(),
onDelete: () => this.fetchSnapshots(),
}),
reloadBackup: () => this.refreshData(),
reloadSnapshot: () => this.refreshData(),
});
}
private async fetchBackups() {
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
private async fetchSnapshots() {
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
}
private async _deleteSelected() {
const confirm = await showConfirmationDialog(this, {
title: this.supervisor.localize("backup.delete_backup_title"),
text: this.supervisor.localize("backup.delete_backup_text", {
number: this._selectedBackups.length,
title: this.supervisor.localize("snapshot.delete_snapshot_title"),
text: this.supervisor.localize("snapshot.delete_snapshot_text", {
number: this._selectedSnapshots.length,
}),
confirmText: this.supervisor.localize("backup.delete_backup_confirm"),
confirmText: this.supervisor.localize("snapshot.delete_snapshot_confirm"),
});
if (!confirm) {
@@ -292,44 +292,44 @@ export class HassioBackups extends LitElement {
try {
await Promise.all(
this._selectedBackups.map((slug) => removeBackup(this.hass, slug))
this._selectedSnapshots.map((slug) => removeSnapshot(this.hass, slug))
);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("backup.failed_to_delete"),
title: this.supervisor.localize("snapshot.failed_to_delete"),
text: extractApiErrorMessage(err),
});
return;
}
await reloadHassioBackups(this.hass);
this._backups = await fetchHassioBackups(this.hass);
await reloadHassioSnapshots(this.hass);
this._snapshots = await fetchHassioSnapshots(this.hass);
this._dataTable.clearSelection();
}
private _handleRowClicked(ev: HASSDomEvent<RowClickedEvent>) {
const slug = ev.detail.id;
showHassioBackupDialog(this, {
showHassioSnapshotDialog(this, {
slug,
supervisor: this.supervisor,
onDelete: () => this.fetchBackups(),
onDelete: () => this.fetchSnapshots(),
});
}
private _createBackup() {
private _createSnapshot() {
if (this.supervisor!.info.state !== "running") {
showAlertDialog(this, {
title: this.supervisor!.localize("backup.could_not_create"),
title: this.supervisor!.localize("snapshot.could_not_create"),
text: this.supervisor!.localize(
"backup.create_blocked_not_running",
"snapshot.create_blocked_not_running",
"state",
this.supervisor!.info.state
),
});
return;
}
showHassioCreateBackupDialog(this, {
showHassioCreateSnapshotDialog(this, {
supervisor: this.supervisor!,
onCreate: () => this.fetchBackups(),
onCreate: () => this.fetchSnapshots(),
});
}
@@ -378,6 +378,6 @@ export class HassioBackups extends LitElement {
declare global {
interface HTMLElementTagNameMap {
"hassio-backups": HassioBackups;
"hassio-snapshots": HassioSnapshots;
}
}

View File

@@ -86,8 +86,10 @@ export class SupervisorBaseElement extends urlSyncMixin(
const unsubs = Object.keys(this._unsubs);
for (const collection of Object.keys(this._collections)) {
if (!unsubs.includes(collection)) {
this._unsubs[collection] = this._collections[collection].subscribe(
(data) => this._updateSupervisor({ [collection]: data })
this._unsubs[collection] = this._collections[
collection
].subscribe((data) =>
this._updateSupervisor({ [collection]: data })
);
}
}

View File

@@ -144,7 +144,7 @@ class HassioCoreInfo extends LitElement {
try {
await restartCore(this.hass);
} catch (err: any) {
} catch (err) {
if (this.hass.connection.connected) {
showAlertDialog(this, {
title: this.supervisor.localize(
@@ -165,7 +165,7 @@ class HassioCoreInfo extends LitElement {
supervisor: this.supervisor,
name: "Home Assistant Core",
version: this.supervisor.core.version_latest,
backupParams: {
snapshotParams: {
name: `core_${this.supervisor.core.version}`,
folders: ["homeassistant"],
homeassistant: true,

View File

@@ -1,6 +1,8 @@
import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js";
import { dump } from "js-yaml";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators";
import memoizeOne from "memoize-one";
@@ -39,8 +41,7 @@ import {
roundWithOneDecimal,
} from "../../../src/util/calculate";
import "../components/supervisor-metric";
import { showHassioDatadiskDialog } from "../dialogs/datadisk/show-dialog-hassio-datadisk";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { showHassioMarkdownDialog } from "../dialogs/markdown/show-dialog-hassio-markdown";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { hassioStyle } from "../resources/hassio-style";
@@ -113,7 +114,7 @@ class HassioHostInfo extends LitElement {
`
: ""}
</ha-settings-row>
${!this.supervisor.host.features.includes("haos")
${!this.supervisor.host.features.includes("hassos")
? html`<ha-settings-row>
<span slot="heading">
${this.supervisor.localize("system.host.docker_version")}
@@ -180,38 +181,20 @@ class HassioHostInfo extends LitElement {
`
: ""}
<ha-button-menu corner="BOTTOM_START">
<ha-button-menu
corner="BOTTOM_START"
@action=${this._handleMenuAction}
>
<mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button>
<mwc-list-item
.action=${"hardware"}
@click=${this._handleMenuAction}
>
<mwc-list-item>
${this.supervisor.localize("system.host.hardware")}
</mwc-list-item>
${this.supervisor.host.features.includes("haos")
? html`
<mwc-list-item
.action=${"import_from_usb"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>
${this.supervisor.host.features.includes("os_agent") &&
atLeastVersion(this.supervisor.host.agent_version, 1, 2, 0)
? html`
<mwc-list-item
.action=${"move_datadisk"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize(
"system.host.move_datadisk"
)}
</mwc-list-item>
`
: ""}
`
${this.supervisor.host.features.includes("hassos")
? html`<mwc-list-item>
${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item>`
: ""}
</ha-button-menu>
</div>
@@ -234,40 +217,32 @@ class HassioHostInfo extends LitElement {
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
});
private async _handleMenuAction(ev) {
switch ((ev.target as any).action) {
case "hardware":
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch (ev.detail.index) {
case 0:
await this._showHardware();
break;
case "import_from_usb":
case 1:
await this._importFromUSB();
break;
case "move_datadisk":
await this._moveDatadisk();
break;
}
}
private _moveDatadisk(): void {
showHassioDatadiskDialog(this, {
supervisor: this.supervisor,
});
}
private async _showHardware(): Promise<void> {
let hardware;
try {
hardware = await fetchHassioHardwareInfo(this.hass);
} catch (err: any) {
await showAlertDialog(this, {
const content = await fetchHassioHardwareInfo(this.hass);
showHassioMarkdownDialog(this, {
title: this.supervisor.localize("system.host.hardware"),
content: `<pre>${dump(content, { indent: 2 })}</pre>`,
});
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.host.failed_to_get_hardware_list"
),
text: extractApiErrorMessage(err),
});
return;
}
showHassioHardwareDialog(this, { supervisor: this.supervisor, hardware });
}
private async _hostReboot(ev: CustomEvent): Promise<void> {
@@ -288,7 +263,7 @@ class HassioHostInfo extends LitElement {
try {
await rebootHost(this.hass);
} catch (err: any) {
} catch (err) {
// Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
@@ -318,7 +293,7 @@ class HassioHostInfo extends LitElement {
try {
await shutdownHost(this.hass);
} catch (err: any) {
} catch (err) {
// Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, {
@@ -359,7 +334,7 @@ class HassioHostInfo extends LitElement {
try {
await updateOS(this.hass);
fireEvent(this, "supervisor-collection-refresh", { collection: "os" });
} catch (err: any) {
} catch (err) {
if (this.hass.connection.connected) {
showAlertDialog(this, {
title: this.supervisor.localize(
@@ -397,7 +372,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", {
collection: "host",
});
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_set_hostname"),
text: extractApiErrorMessage(err),
@@ -412,7 +387,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", {
collection: "host",
});
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.host.failed_to_import_from_usb"

View File

@@ -3,7 +3,6 @@ import { customElement, property, state } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch";
@@ -34,18 +33,16 @@ import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON_URL = {
apparmor: "/more-info/unsupported/apparmor",
container: "/more-info/unsupported/container",
content_trust: "/more-info/unsupported/content_trust",
dbus: "/more-info/unsupported/dbus",
docker_configuration: "/more-info/unsupported/docker_configuration",
docker_version: "/more-info/unsupported/docker_version",
job_conditions: "/more-info/unsupported/job_conditions",
lxc: "/more-info/unsupported/lxc",
network_manager: "/more-info/unsupported/network_manager",
os_agent: "/more-info/unsupported/os_agent",
os: "/more-info/unsupported/os",
privileged: "/more-info/unsupported/privileged",
source_mods: "/more-info/unsupported/source_mods",
systemd: "/more-info/unsupported/systemd",
content_trust: "/more-info/unsupported/content_trust",
};
const UNHEALTHY_REASON_URL = {
@@ -173,25 +170,31 @@ class HassioSupervisorInfo extends LitElement {
></ha-switch>
</ha-settings-row>`
: ""
: html`<ha-alert
alert-type="warning"
.actionText=${this.supervisor.localize("common.learn_more")}
@alert-action-clicked=${this._unsupportedDialog}
>
: html`<div class="error">
${this.supervisor.localize(
"system.supervisor.unsupported_title"
)}
</ha-alert>`}
<button
class="link"
.title=${this.supervisor.localize("common.learn_more")}
@click=${this._unsupportedDialog}
>
Learn more
</button>
</div>`}
${!this.supervisor.supervisor.healthy
? html`<ha-alert
alert-type="error"
.actionText=${this.supervisor.localize("common.learn_more")}
@alert-action-clicked=${this._unhealthyDialog}
>
? html`<div class="error">
${this.supervisor.localize(
"system.supervisor.unhealthy_title"
)}
</ha-alert>`
<button
class="link"
.title=${this.supervisor.localize("common.learn_more")}
@click=${this._unhealthyDialog}
>
Learn more
</button>
</div>`
: ""}
</div>
<div class="metrics-block">
@@ -282,7 +285,7 @@ class HassioSupervisorInfo extends LitElement {
};
await setSupervisorOption(this.hass, data);
await this._reloadSupervisor();
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.supervisor.failed_to_set_option"
@@ -300,7 +303,7 @@ class HassioSupervisorInfo extends LitElement {
try {
await this._reloadSupervisor();
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize("system.supervisor.failed_to_reload"),
text: extractApiErrorMessage(err),
@@ -343,7 +346,7 @@ class HassioSupervisorInfo extends LitElement {
try {
await restartSupervisor(this.hass);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_restart_name",
@@ -388,7 +391,7 @@ class HassioSupervisorInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor",
});
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"common.failed_to_update_name",
@@ -427,10 +430,10 @@ class HassioSupervisorInfo extends LitElement {
<li>
${UNSUPPORTED_REASON_URL[reason]
? html`<a
href=${documentationUrl(
href="${documentationUrl(
this.hass,
UNSUPPORTED_REASON_URL[reason]
)}
)}"
target="_blank"
rel="noreferrer"
>
@@ -458,10 +461,10 @@ class HassioSupervisorInfo extends LitElement {
<li>
${UNHEALTHY_REASON_URL[reason]
? html`<a
href=${documentationUrl(
href="${documentationUrl(
this.hass,
UNHEALTHY_REASON_URL[reason]
)}
)}"
target="_blank"
rel="noreferrer"
>
@@ -483,7 +486,7 @@ class HassioSupervisorInfo extends LitElement {
diagnostics: !this.supervisor.supervisor?.diagnostics,
};
await setSupervisorOption(this.hass, data);
} catch (err: any) {
} catch (err) {
showAlertDialog(this, {
title: this.supervisor.localize(
"system.supervisor.failed_to_set_option"

View File

@@ -5,7 +5,6 @@ import "@polymer/paper-listbox/paper-listbox";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators";
import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card";
import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
@@ -68,9 +67,7 @@ class HassioSupervisorLog extends LitElement {
protected render(): TemplateResult | void {
return html`
<ha-card>
${this._error
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
${this.hass.userData?.showAdvanced
? html`
<paper-dropdown-menu
@@ -130,7 +127,7 @@ class HassioSupervisorLog extends LitElement {
this.hass,
this._selectedLogProvider
);
} catch (err: any) {
} catch (err) {
this._error = this.supervisor.localize(
"system.log.get_logs",
"provider",
@@ -157,6 +154,10 @@ class HassioSupervisorLog extends LitElement {
padding: 0 2%;
width: 96%;
}
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
`,
];
}

View File

@@ -1,4 +1,5 @@
module.exports = {
"*.{js,ts}": 'eslint --ignore-pattern "**/build-scripts/**/*.js" --fix',
"*.ts": () => "tsc -p tsconfig.json",
"*.{js,ts}": "eslint --fix",
"!(/translations)*.{js,ts,json,css,md,html}": "prettier --write",
};

View File

@@ -1,3 +0,0 @@
[build.environment]
YARN_VERSION = "1.22.11"
NODE_OPTIONS = "--max_old_space_size=6144"

View File

@@ -16,120 +16,121 @@
"lint:lit": "lit-analyzer \"**/src/**/*.ts\" --format markdown --outFile result.md",
"lint": "yarn run lint:eslint && yarn run lint:prettier && yarn run lint:types",
"format": "yarn run format:eslint && yarn run format:prettier",
"test": "instant-mocha --webpack-config ./test/webpack.config.js --require ./test/setup.js \"test/**/*.ts\""
"mocha": "ts-mocha -p test-mocha/tsconfig.test.json \"test-mocha/**/*.ts\"",
"test": "yarn run lint && yarn run mocha"
},
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
"license": "Apache-2.0",
"dependencies": {
"@braintree/sanitize-url": "^5.0.2",
"@codemirror/commands": "^0.19.2",
"@codemirror/gutter": "^0.19.1",
"@codemirror/highlight": "^0.19.2",
"@codemirror/history": "^0.19.0",
"@codemirror/legacy-modes": "^0.19.0",
"@codemirror/rectangular-selection": "^0.19.0",
"@codemirror/search": "^0.19.0",
"@codemirror/state": "^0.19.1",
"@codemirror/stream-parser": "^0.19.1",
"@codemirror/text": "^0.19.2",
"@codemirror/view": "^0.19.4",
"@formatjs/intl-datetimeformat": "^4.2.4",
"@formatjs/intl-getcanonicallocales": "^1.7.3",
"@formatjs/intl-locale": "^2.4.38",
"@formatjs/intl-numberformat": "^7.2.4",
"@formatjs/intl-pluralrules": "^4.1.4",
"@formatjs/intl-relativetimeformat": "^9.3.1",
"@formatjs/intl-utils": "^3.8.4",
"@fullcalendar/common": "5.9.0",
"@fullcalendar/core": "5.9.0",
"@fullcalendar/daygrid": "5.9.0",
"@fullcalendar/interaction": "5.9.0",
"@fullcalendar/list": "5.9.0",
"@lit-labs/virtualizer": "patch:@lit-labs/virtualizer@0.6.0#./.yarn/patches/@lit-labs/virtualizer/0.7.0.patch",
"@material/chips": "13.0.0-canary.65125b3a6.0",
"@material/data-table": "13.0.0-canary.65125b3a6.0",
"@material/mwc-button": "0.25.1",
"@material/mwc-checkbox": "0.25.1",
"@material/mwc-circular-progress": "0.25.1",
"@material/mwc-dialog": "0.25.1",
"@material/mwc-fab": "0.25.1",
"@material/mwc-formfield": "0.25.1",
"@material/mwc-icon-button": "0.25.1",
"@material/mwc-linear-progress": "0.25.1",
"@material/mwc-list": "0.25.1",
"@material/mwc-menu": "0.25.1",
"@material/mwc-radio": "0.25.1",
"@material/mwc-ripple": "0.25.1",
"@material/mwc-switch": "0.25.1",
"@material/mwc-tab": "0.25.1",
"@material/mwc-tab-bar": "0.25.1",
"@material/top-app-bar": "13.0.0-canary.65125b3a6.0",
"@mdi/js": "6.1.95",
"@mdi/svg": "6.1.95",
"@polymer/app-layout": "^3.1.0",
"@braintree/sanitize-url": "^5.0.1",
"@codemirror/commands": "^0.18.0",
"@codemirror/gutter": "^0.18.0",
"@codemirror/highlight": "^0.18.0",
"@codemirror/history": "^0.18.0",
"@codemirror/legacy-modes": "^0.18.0",
"@codemirror/rectangular-selection": "^0.18.0",
"@codemirror/search": "^0.18.0",
"@codemirror/state": "^0.18.0",
"@codemirror/stream-parser": "^0.18.0",
"@codemirror/text": "^0.18.0",
"@codemirror/view": "^0.18.0",
"@formatjs/intl-getcanonicallocales": "^1.5.10",
"@formatjs/intl-locale": "^2.4.28",
"@formatjs/intl-pluralrules": "^4.0.22",
"@fullcalendar/common": "5.1.0",
"@fullcalendar/core": "5.1.0",
"@fullcalendar/daygrid": "5.1.0",
"@fullcalendar/interaction": "5.1.0",
"@fullcalendar/list": "5.1.0",
"@lit-labs/virtualizer": "^0.6.0",
"@material/chips": "=12.0.0-canary.1a8d06483.0",
"@material/mwc-button": "canary",
"@material/mwc-checkbox": "canary",
"@material/mwc-circular-progress": "canary",
"@material/mwc-dialog": "canary",
"@material/mwc-fab": "canary",
"@material/mwc-formfield": "canary",
"@material/mwc-icon-button": "canary",
"@material/mwc-list": "canary",
"@material/mwc-menu": "canary",
"@material/mwc-radio": "canary",
"@material/mwc-ripple": "canary",
"@material/mwc-switch": "canary",
"@material/mwc-tab": "canary",
"@material/mwc-tab-bar": "canary",
"@material/top-app-bar": "=12.0.0-canary.1a8d06483.0",
"@mdi/js": "5.9.55",
"@mdi/svg": "5.9.55",
"@polymer/app-layout": "^3.0.2",
"@polymer/app-storage": "^3.0.2",
"@polymer/iron-autogrow-textarea": "^3.0.1",
"@polymer/iron-flex-layout": "^3.0.1",
"@polymer/iron-icon": "^3.0.1",
"@polymer/iron-image": "^3.0.1",
"@polymer/iron-input": "^3.0.1",
"@polymer/iron-overlay-behavior": "^3.0.3",
"@polymer/iron-label": "^3.0.1",
"@polymer/iron-overlay-behavior": "^3.0.2",
"@polymer/iron-resizable-behavior": "^3.0.1",
"@polymer/paper-checkbox": "^3.1.0",
"@polymer/paper-dialog": "^3.0.1",
"@polymer/paper-dialog-behavior": "^3.0.1",
"@polymer/paper-dialog-scrollable": "^3.0.1",
"@polymer/paper-dropdown-menu": "^3.2.0",
"@polymer/paper-input": "^3.2.1",
"@polymer/paper-dropdown-menu": "^3.0.1",
"@polymer/paper-input": "^3.0.1",
"@polymer/paper-item": "^3.0.1",
"@polymer/paper-listbox": "^3.0.1",
"@polymer/paper-menu-button": "^3.1.0",
"@polymer/paper-menu-button": "^3.0.1",
"@polymer/paper-progress": "^3.0.1",
"@polymer/paper-radio-button": "^3.0.1",
"@polymer/paper-radio-group": "^3.0.1",
"@polymer/paper-ripple": "^3.0.2",
"@polymer/paper-ripple": "^3.0.1",
"@polymer/paper-slider": "^3.0.1",
"@polymer/paper-styles": "^3.0.1",
"@polymer/paper-tabs": "^3.1.0",
"@polymer/paper-tabs": "^3.0.1",
"@polymer/paper-toast": "^3.0.1",
"@polymer/paper-tooltip": "^3.0.1",
"@polymer/polymer": "3.4.1",
"@thomasloven/round-slider": "0.5.4",
"@vaadin/vaadin-combo-box": "^21.0.2",
"@vaadin/vaadin-date-picker": "^21.0.2",
"@polymer/polymer": "3.1.0",
"@thomasloven/round-slider": "0.5.2",
"@vaadin/vaadin-combo-box": "^5.0.10",
"@vaadin/vaadin-date-picker": "^4.0.7",
"@vibrant/color": "^3.2.1-alpha.1",
"@vibrant/core": "^3.2.1-alpha.1",
"@vibrant/quantizer-mmcq": "^3.2.1-alpha.1",
"@vue/web-component-wrapper": "^1.2.0",
"@webcomponents/webcomponentsjs": "^2.2.10",
"chart.js": "^3.3.2",
"@webcomponents/webcomponentsjs": "^2.2.7",
"chart.js": "^2.9.4",
"chartjs-chart-timeline": "^0.4.0",
"comlink": "^4.3.1",
"core-js": "^3.15.2",
"cropperjs": "^1.5.12",
"date-fns": "^2.23.0",
"core-js": "^3.6.5",
"cropperjs": "^1.5.11",
"deep-clone-simple": "^1.1.1",
"deep-freeze": "^0.0.1",
"fecha": "^4.2.0",
"fuse.js": "^6.0.0",
"google-timezones-json": "^1.0.2",
"hls.js": "^1.0.10",
"home-assistant-js-websocket": "^5.11.1",
"idb-keyval": "^5.1.3",
"intl-messageformat": "^9.9.1",
"hls.js": "^1.0.4",
"home-assistant-js-websocket": "^5.10.0",
"idb-keyval": "^5.0.5",
"intl-messageformat": "^9.6.16",
"js-yaml": "^4.1.0",
"leaflet": "^1.7.1",
"leaflet-draw": "^1.0.4",
"lit": "^2.0.0",
"lit-vaadin-helpers": "^0.2.1",
"marked": "^3.0.2",
"lit": "^2.0.0-rc.2",
"lit-vaadin-helpers": "^0.1.3",
"marked": "^2.0.5",
"mdn-polyfills": "^5.16.0",
"memoize-one": "^5.2.1",
"node-vibrant": "3.2.1-alpha.1",
"proxy-polyfill": "^0.3.2",
"proxy-polyfill": "^0.3.1",
"punycode": "^2.1.1",
"qrcode": "^1.4.4",
"regenerator-runtime": "^0.13.8",
"regenerator-runtime": "^0.13.2",
"resize-observer-polyfill": "^1.5.1",
"roboto-fontface": "^0.10.0",
"sortablejs": "^1.14.0",
"sortablejs": "^1.10.2",
"superstruct": "^0.15.2",
"tinykeys": "^1.1.3",
"tsparticles": "^1.34.0",
"tsparticles": "^1.19.2",
"unfetch": "^4.1.0",
"vis-data": "^7.1.2",
"vis-network": "^8.5.4",
@@ -145,18 +146,17 @@
"xss": "^1.0.9"
},
"devDependencies": {
"@babel/core": "^7.15.5",
"@babel/plugin-external-helpers": "^7.14.5",
"@babel/plugin-proposal-class-properties": "^7.14.5",
"@babel/plugin-proposal-decorators": "^7.15.4",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5",
"@babel/plugin-proposal-object-rest-spread": "^7.15.6",
"@babel/plugin-proposal-optional-chaining": "^7.14.5",
"@babel/core": "^7.14.3",
"@babel/plugin-external-helpers": "^7.12.13",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-decorators": "^7.13.15",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8",
"@babel/plugin-proposal-object-rest-spread": "^7.13.8",
"@babel/plugin-proposal-optional-chaining": "^7.13.12",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/plugin-syntax-top-level-await": "^7.14.5",
"@babel/preset-env": "^7.15.6",
"@babel/preset-typescript": "^7.15.0",
"@babel/preset-env": "^7.14.2",
"@babel/preset-typescript": "^7.13.0",
"@koa/cors": "^3.1.0",
"@open-wc/dev-server-hmr": "^0.0.2",
"@rollup/plugin-babel": "^5.2.1",
@@ -166,31 +166,30 @@
"@rollup/plugin-replace": "^2.3.2",
"@types/chromecast-caf-receiver": "5.0.12",
"@types/chromecast-caf-sender": "^1.0.3",
"@types/js-yaml": "^4",
"@types/leaflet": "^1",
"@types/leaflet-draw": "^1",
"@types/marked": "^2",
"@types/mocha": "^8",
"@types/sortablejs": "^1",
"@types/js-yaml": "^4.0.1",
"@types/leaflet": "^1.7.0",
"@types/leaflet-draw": "^1.0.3",
"@types/marked": "^2.0.3",
"@types/mocha": "^8.2.2",
"@types/sortablejs": "^1.10.6",
"@types/webspeechapi": "^0.0.29",
"@typescript-eslint/eslint-plugin": "^4.32.0",
"@typescript-eslint/parser": "^4.32.0",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"@web/dev-server": "^0.0.24",
"@web/dev-server-rollup": "^0.2.11",
"babel-loader": "^8.2.2",
"babel-loader": "^8.1.0",
"chai": "^4.3.4",
"cpx": "^1.5.0",
"del": "^4.0.0",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-airbnb-typescript": "^14.0.0",
"eslint": "^7.25.0",
"eslint-config-airbnb-typescript": "^12.3.1",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.1",
"eslint-import-resolver-webpack": "^0.13.0",
"eslint-plugin-disable": "^2.0.1",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-lit": "^1.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-unused-imports": "^1.1.5",
"eslint-plugin-wc": "^1.3.2",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-lit": "^1.3.0",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-wc": "^1.3.0",
"fancy-log": "^1.3.3",
"fs-extra": "^7.0.1",
"gulp": "^4.0.2",
@@ -201,8 +200,7 @@
"gulp-zopfli-green": "^3.0.1",
"html-minifier": "^4.0.0",
"husky": "^1.3.1",
"instant-mocha": "^1.3.1",
"lint-staged": "^11.1.2",
"lint-staged": "^10.5.4",
"lit-analyzer": "^1.2.1",
"lodash.template": "^4.5.0",
"magic-string": "^0.25.7",
@@ -211,7 +209,8 @@
"mocha": "^8.4.0",
"object-hash": "^2.0.3",
"open": "^7.0.4",
"prettier": "^2.4.1",
"prettier": "^2.0.4",
"raw-loader": "^2.0.0",
"require-dir": "^1.2.0",
"rollup": "^2.8.2",
"rollup-plugin-string": "^3.0.0",
@@ -221,26 +220,25 @@
"sinon": "^11.0.0",
"source-map-url": "^0.4.0",
"systemjs": "^6.3.2",
"terser-webpack-plugin": "^5.2.4",
"terser-webpack-plugin": "^5.1.2",
"ts-lit-plugin": "^1.2.1",
"typescript": "^4.4.3",
"ts-mocha": "^8.0.0",
"typescript": "^4.2.4",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"webpack": "^5.55.1",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.3.0",
"webpack-manifest-plugin": "^4.0.2",
"webpackbar": "^5.0.0-3",
"webpack": "^5.24.1",
"webpack-cli": "^4.5.0",
"webpack-dev-server": "^3.11.2",
"webpack-manifest-plugin": "^3.0.0",
"workbox-build": "^6.1.5"
},
"_comment": "Polymer 3.2 contained a bug, fixed in https://github.com/Polymer/polymer/pull/5569, add as patch",
"_comment": "Polymer fixed to 3.1 because 3.2 throws on logbook page",
"_comment_2": "Fix in https://github.com/Polymer/polymer/pull/5569",
"resolutions": {
"@polymer/polymer": "patch:@polymer/polymer@3.4.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
"@webcomponents/webcomponentsjs": "^2.2.10",
"lit": "^2.0.0",
"lit-html": "2.0.0",
"lit-element": "3.0.0",
"@lit/reactive-element": "1.0.0"
"@polymer/polymer": "3.1.0",
"lit-html": "2.0.0-rc.3",
"lit-element": "3.0.0-rc.2"
},
"main": "src/home-assistant.js",
"husky": {

View File

@@ -9,6 +9,12 @@ if [ -z "${DEVCONTAINER}" ]; then
exit 1
fi
if [ ! -z "${CODESPACES}" ]; then
WORKSPACE="/root/workspace/frontend"
else
WORKSPACE="/workspaces/frontend"
fi
if [ -z $(which hass) ]; then
echo "Installing Home Asstant core from dev."
python3 -m pip install --upgrade \
@@ -16,9 +22,9 @@ if [ -z $(which hass) ]; then
git+git://github.com/home-assistant/home-assistant.git@dev
fi
if [ ! -d "/workspaces/frontend/config" ]; then
if [ ! -d "${WORKSPACE}/config" ]; then
echo "Creating default configuration."
mkdir -p "/workspaces/frontend/config";
mkdir -p "${WORKSPACE}/config";
hass --script ensure_config -c config
echo "demo:
@@ -26,24 +32,24 @@ logger:
default: info
logs:
homeassistant.components.frontend: debug
" >> /workspaces/frontend/config/configuration.yaml
" >> "${WORKSPACE}/config/configuration.yaml"
if [ ! -z "${HASSIO}" ]; then
echo "
# frontend:
# development_repo: /workspaces/frontend
# development_repo: ${WORKSPACE}
hassio:
development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
else
echo "
frontend:
development_repo: /workspaces/frontend
development_repo: ${WORKSPACE}
# hassio:
# development_repo: /workspaces/frontend" >> /workspaces/frontend/config/configuration.yaml
# development_repo: ${WORKSPACE}" >> "${WORKSPACE}/config/configuration.yaml"
fi
fi
hass -c /workspaces/frontend/config
hass -c "${WORKSPACE}/config"

View File

@@ -2,9 +2,9 @@ from setuptools import setup, find_packages
setup(
name="home-assistant-frontend",
version="20210930.0",
version="20210531.0",
description="The Home Assistant frontend",
url="https://github.com/home-assistant/frontend",
url="https://github.com/home-assistant/home-assistant-polymer",
author="The Home Assistant Authors",
author_email="hello@home-assistant.io",
license="Apache-2.0",

Some files were not shown because too many files have changed in this diff Show More