Compare commits

..

1 Commits

Author SHA1 Message Date
Joakim Sørensen
0bfeb22209 Move snapshot toggle to persistent checkbox 2021-06-15 16:40:24 +00:00
851 changed files with 36301 additions and 60671 deletions

View File

@@ -1,10 +1,9 @@
{ {
"extends": [ "extends": [
"airbnb-base",
"airbnb-typescript/base", "airbnb-typescript/base",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:wc/recommended", "plugin:wc/recommended",
"plugin:lit/all", "plugin:lit/recommended",
"prettier" "prettier"
], ],
"parser": "@typescript-eslint/parser", "parser": "@typescript-eslint/parser",
@@ -29,7 +28,6 @@
"__BUILD__": false, "__BUILD__": false,
"__VERSION__": false, "__VERSION__": false,
"__STATIC_PATH__": false, "__STATIC_PATH__": false,
"__SUPERVISOR__": false,
"Polymer": true "Polymer": true
}, },
"env": { "env": {
@@ -37,51 +35,55 @@
"es6": true "es6": true
}, },
"rules": { "rules": {
"class-methods-use-this": "off", "class-methods-use-this": 0,
"new-cap": "off", "new-cap": 0,
"prefer-template": "off", "prefer-template": 0,
"object-shorthand": "off", "object-shorthand": 0,
"func-names": "off", "func-names": 0,
"no-underscore-dangle": "off", "prefer-arrow-callback": 0,
"strict": "off", "no-underscore-dangle": 0,
"no-plusplus": "off", "strict": 0,
"no-bitwise": "error", "prefer-spread": 0,
"comma-dangle": "off", "no-plusplus": 0,
"vars-on-top": "off", "no-bitwise": 2,
"no-continue": "off", "comma-dangle": 0,
"no-param-reassign": "off", "vars-on-top": 0,
"no-multi-assign": "off", "no-continue": 0,
"no-console": "error", "no-param-reassign": 0,
"radix": "off", "no-multi-assign": 0,
"no-alert": "off", "no-console": 2,
"no-nested-ternary": "off", "radix": 0,
"prefer-destructuring": "off", "no-alert": 0,
"no-return-await": 0,
"no-nested-ternary": 0,
"prefer-destructuring": 0,
"no-restricted-globals": [2, "event"], "no-restricted-globals": [2, "event"],
"prefer-promise-reject-errors": "off", "prefer-promise-reject-errors": 0,
"import/prefer-default-export": "off", "import/order": 0,
"import/no-default-export": "off", "import/prefer-default-export": 0,
"import/no-unresolved": "off", "import/no-unresolved": 0,
"import/no-cycle": "off", "import/no-cycle": 0,
"import/extensions": [ "import/extensions": [
"error", 2,
"ignorePackages", "ignorePackages",
{ "ts": "never", "js": "never" } { "ts": "never", "js": "never" }
], ],
"no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"], "no-restricted-syntax": ["error", "LabeledStatement", "WithStatement"],
"object-curly-newline": "off", "object-curly-newline": 0,
"default-case": "off", "default-case": 0,
"wc/no-self-class": "off", "wc/no-self-class": 0,
"no-shadow": "off", "no-shadow": 0,
"@typescript-eslint/camelcase": "off", "@typescript-eslint/camelcase": 0,
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-non-null-assertion": 0,
"@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-unused-vars": 0,
"@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-shadow": ["error"], "@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/naming-convention": [ "@typescript-eslint/naming-convention": [
"off", 0,
{ {
"selector": "default", "selector": "default",
"format": ["camelCase", "snake_case"], "format": ["camelCase", "snake_case"],
@@ -99,21 +101,9 @@
"format": ["PascalCase"] "format": ["PascalCase"]
} }
], ],
"@typescript-eslint/no-unused-vars": "off", "lit/attribute-value-entities": 0
"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"
}, },
"plugins": ["disable", "unused-imports"], "plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
"processor": "disable/disable" "processor": "disable/disable",
"ignorePatterns": ["src/resources/lit-virtualizer/*"]
} }

View File

@@ -10,64 +10,83 @@ on:
- dev - dev
- master - master
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - 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 - name: Install dependencies
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Build resources - name: Build resources
run: ./node_modules/.bin/gulp gen-icons-json build-translations build-locale-data gather-gallery-demos run: ./node_modules/.bin/gulp gen-icons-json build-translations gather-gallery-demos
- name: Run eslint - name: Run eslint
run: yarn run lint:eslint run: yarn run lint:eslint
- name: Run tsc - name: Run tsc
run: yarn run lint:types run: yarn run lint:types
- name: Run prettier - name: Run prettier
run: yarn run lint:prettier run: yarn run lint:prettier
- name: Check for duplicate dependencies
run: yarn dedupe --check
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - 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 - name: Install dependencies
run: yarn install run: yarn install
env: env:
CI: true CI: true
- name: Build resources - name: Run Mocha
run: ./node_modules/.bin/gulp build-translations build-locale-data run: npm run mocha
- name: Run Tests
run: yarn run test
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: [lint, test] needs: [lint, test]
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - 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 - name: Install dependencies
run: yarn install run: yarn install
env: env:
@@ -82,11 +101,20 @@ jobs:
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - 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 - name: Install dependencies
run: yarn install run: yarn install
env: env:

View File

@@ -4,22 +4,26 @@ on:
push: push:
branches: branches:
- dev - dev
env:
NODE_VERSION: 14
NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out files from GitHub - name: Check out files from GitHub
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Set up Node ${{ env.NODE_VERSION }} - name: Setting up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v1
with: with:
node-version: ${{ env.NODE_VERSION }} node-version: 12.x
cache: yarn - 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 - name: Install dependencies
run: yarn install run: yarn install
env: env:

View File

@@ -7,8 +7,7 @@ on:
env: env:
PYTHON_VERSION: 3.8 PYTHON_VERSION: 3.8
NODE_VERSION: 14 NODE_VERSION: 12.1
NODE_OPTIONS: --max_old_space_size=6144
jobs: jobs:
release: release:
@@ -30,15 +29,7 @@ jobs:
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ env.NODE_VERSION }} 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 - name: Build and release package
run: | run: |
python3 -m pip install twine python3 -m pip install twine
@@ -73,7 +64,8 @@ jobs:
matrix: matrix:
arch: ["aarch64", "armhf", "armv7", "amd64", "i386"] arch: ["aarch64", "armhf", "armv7", "amd64", "i386"]
tag: tag:
- "3.9-alpine3.14" - "3.8-alpine3.12"
- "3.9-alpine3.13"
steps: steps:
- name: Download requirements.txt - name: Download requirements.txt
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2

View File

@@ -1,6 +1,8 @@
name: Translations name: Translations
on: on:
schedule:
- cron: "30 0 * * *"
push: push:
branches: branches:
- dev - dev
@@ -8,7 +10,7 @@ on:
- src/translations/en.json - src/translations/en.json
env: env:
NODE_VERSION: 14 NODE_VERSION: 12
jobs: jobs:
upload: upload:
@@ -18,8 +20,46 @@ jobs:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 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 - name: Upload Translations
run: | run: |
export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}" export LOKALISE_TOKEN="${{ secrets.LOKALISE_TOKEN }}"
./script/translations_upload_base ./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

11
.gitignore vendored
View File

@@ -3,19 +3,14 @@
# build # build
build build
build-translations/*
hass_frontend/* hass_frontend/*
dist dist
# yarn # yarn
.yarn/* .yarn
!.yarn/patches
!.yarn/releases
!.yarn/plugins
!.yarn/sdks
!.yarn/versions
.pnp.*
node_modules/*
yarn-error.log yarn-error.log
node_modules/*
npm-debug.log npm-debug.log
# Python stuff # Python stuff

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

View File

@@ -1,4 +1,5 @@
build build
build-translations/*
translations/* translations/*
node_modules/* node_modules/*
hass_frontend/* hass_frontend/*

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 // Files from NPM Packages that should not be imported
module.exports.ignorePackages = ({ latestBuild }) => [ 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 // Part of yaml.js and only used for !!js functions that we don't use
require.resolve("esprima"), require.resolve("esprima"),
]; ];
@@ -18,8 +20,7 @@ module.exports.emptyPackages = ({ latestBuild }) =>
require.resolve("@polymer/paper-styles/default-theme.js"), require.resolve("@polymer/paper-styles/default-theme.js"),
// Loads stuff from a CDN // Loads stuff from a CDN
require.resolve("@polymer/font-roboto/roboto.js"), require.resolve("@polymer/font-roboto/roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/typography.js"), require.resolve("@vaadin/vaadin-material-styles/font-roboto.js"),
require.resolve("@vaadin/vaadin-material-styles/font-icons.js"),
// Compatibility not needed for latest builds // Compatibility not needed for latest builds
latestBuild && latestBuild &&
// wrapped in require.resolve so it blows up if file no longer exists // wrapped in require.resolve so it blows up if file no longer exists
@@ -35,7 +36,6 @@ module.exports.definedVars = ({ isProdBuild, latestBuild, defineOverlay }) => ({
__BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"), __BUILD__: JSON.stringify(latestBuild ? "latest" : "es5"),
__VERSION__: JSON.stringify(env.version()), __VERSION__: JSON.stringify(env.version()),
__DEMO__: false, __DEMO__: false,
__SUPERVISOR__: false,
__BACKWARDS_COMPAT__: false, __BACKWARDS_COMPAT__: false,
__STATIC_PATH__: "/static/", __STATIC_PATH__: "/static/",
"process.env.NODE_ENV": JSON.stringify( "process.env.NODE_ENV": JSON.stringify(
@@ -58,23 +58,12 @@ module.exports.babelOptions = ({ latestBuild }) => ({
"@babel/preset-env", "@babel/preset-env",
{ {
useBuiltIns: "entry", useBuiltIns: "entry",
corejs: "3.15", corejs: "3.6",
bugfixes: true,
}, },
], ],
"@babel/preset-typescript", "@babel/preset-typescript",
].filter(Boolean), ].filter(Boolean),
plugins: [ 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}) // Part of ES2018. Converts {...a, b: 2} to Object.assign({}, a, {b: 2})
!latestBuild && [ !latestBuild && [
"@babel/plugin-proposal-object-rest-spread", "@babel/plugin-proposal-object-rest-spread",
@@ -83,19 +72,12 @@ module.exports.babelOptions = ({ latestBuild }) => ({
// Only support the syntax, Webpack will handle it. // Only support the syntax, Webpack will handle it.
"@babel/plugin-syntax-import-meta", "@babel/plugin-syntax-import-meta",
"@babel/plugin-syntax-dynamic-import", "@babel/plugin-syntax-dynamic-import",
"@babel/plugin-syntax-top-level-await",
"@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-optional-chaining",
"@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-nullish-coalescing-operator",
["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }], ["@babel/plugin-proposal-decorators", { decoratorsBeforeExport: true }],
["@babel/plugin-proposal-private-methods", { loose: true }], ["@babel/plugin-proposal-private-methods", { loose: true }],
["@babel/plugin-proposal-private-property-in-object", { loose: true }],
["@babel/plugin-proposal-class-properties", { loose: true }], ["@babel/plugin-proposal-class-properties", { loose: true }],
].filter(Boolean), ].filter(Boolean),
exclude: [
// \\ for Windows, / for Mac OS and Linux
/node_modules[\\/]core-js/,
/node_modules[\\/]webpack[\\/]buildin/,
],
}); });
const outputPath = (outputRoot, latestBuild) => const outputPath = (outputRoot, latestBuild) =>
@@ -195,9 +177,6 @@ module.exports.config = {
publicPath: publicPath(latestBuild, paths.hassio_publicPath), publicPath: publicPath(latestBuild, paths.hassio_publicPath),
isProdBuild, isProdBuild,
latestBuild, latestBuild,
defineOverlay: {
__SUPERVISOR__: true,
},
}; };
}, },

View File

@@ -5,7 +5,6 @@ const env = require("../env");
require("./clean.js"); require("./clean.js");
require("./translations.js"); require("./translations.js");
require("./locale-data.js");
require("./gen-icons-json.js"); require("./gen-icons-json.js");
require("./gather-static.js"); require("./gather-static.js");
require("./compress.js"); require("./compress.js");
@@ -27,8 +26,7 @@ gulp.task(
"gen-icons-json", "gen-icons-json",
"gen-pages-dev", "gen-pages-dev",
"gen-index-app-dev", "gen-index-app-dev",
"build-translations", "build-translations"
"build-locale-data"
), ),
"copy-static-app", "copy-static-app",
env.useWDS() env.useWDS()
@@ -46,11 +44,11 @@ gulp.task(
process.env.NODE_ENV = "production"; process.env.NODE_ENV = "production";
}, },
"clean", "clean",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-app", "copy-static-app",
env.useRollup() ? "rollup-prod-app" : "webpack-prod-app", env.useRollup() ? "rollup-prod-app" : "webpack-prod-app",
// Don't compress running tests ...// Don't compress running tests
...(env.isTest() ? [] : ["compress-app"]), (env.isTest() ? [] : ["compress-app"]),
gulp.parallel( gulp.parallel(
"gen-pages-prod", "gen-pages-prod",
"gen-index-app-prod", "gen-index-app-prod",

View File

@@ -18,7 +18,7 @@ gulp.task(
}, },
"clean-cast", "clean-cast",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast", "copy-static-cast",
"gen-index-cast-dev", "gen-index-cast-dev",
env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast" env.useRollup() ? "rollup-dev-server-cast" : "webpack-dev-server-cast"
@@ -33,7 +33,7 @@ gulp.task(
}, },
"clean-cast", "clean-cast",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-cast", "copy-static-cast",
env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast", env.useRollup() ? "rollup-prod-cast" : "webpack-prod-cast",
"gen-index-cast-prod" "gen-index-cast-prod"

View File

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

View File

@@ -20,12 +20,7 @@ gulp.task(
}, },
"clean-demo", "clean-demo",
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel( gulp.parallel("gen-icons-json", "gen-index-demo-dev", "build-translations"),
"gen-icons-json",
"gen-index-demo-dev",
"build-translations",
"build-locale-data"
),
"copy-static-demo", "copy-static-demo",
env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo" env.useRollup() ? "rollup-dev-server-demo" : "webpack-dev-server-demo"
) )
@@ -40,7 +35,7 @@ gulp.task(
"clean-demo", "clean-demo",
// Cast needs to be backwards compatible and older HA has no translations // Cast needs to be backwards compatible and older HA has no translations
"translations-enable-merge-backend", "translations-enable-merge-backend",
gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), gulp.parallel("gen-icons-json", "build-translations"),
"copy-static-demo", "copy-static-demo",
env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo", env.useRollup() ? "rollup-prod-demo" : "webpack-prod-demo",
"gen-index-demo-prod" "gen-index-demo-prod"

View File

@@ -302,23 +302,15 @@ gulp.task("gen-index-hassio-prod", async () => {
function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) { function writeHassioEntrypoint(latestEntrypoint, es5Entrypoint) {
fs.mkdirSync(paths.hassio_output_root, { recursive: true }); 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( fs.writeFileSync(
path.resolve(paths.hassio_output_root, "entrypoint.js"), path.resolve(paths.hassio_output_root, "entrypoint.js"),
` `
function loadES5() {
var el = document.createElement('script');
el.src = '${es5Entrypoint}';
document.body.appendChild(el);
}
if (/.*Version\\/(?:11|12)(?:\\.\\d+)*.*Safari\\//.test(navigator.userAgent)) {
loadES5();
} else {
try { try {
new Function("import('${latestEntrypoint}')")(); new Function("import('${latestEntrypoint}')")();
} catch (err) { } catch (err) {
loadES5(); var el = document.createElement('script');
} el.src = '${es5Entrypoint}';
document.body.appendChild(el);
} }
`, `,
{ encoding: "utf-8" } { encoding: "utf-8" }

View File

@@ -51,7 +51,6 @@ gulp.task(
gulp.parallel( gulp.parallel(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data",
"gather-gallery-demos" "gather-gallery-demos"
), ),
"copy-static-gallery", "copy-static-gallery",
@@ -71,7 +70,6 @@ gulp.task(
gulp.parallel( gulp.parallel(
"gen-icons-json", "gen-icons-json",
"build-translations", "build-translations",
"build-locale-data",
"gather-gallery-demos" "gather-gallery-demos"
), ),
"copy-static-gallery", "copy-static-gallery",

View File

@@ -2,6 +2,7 @@
const gulp = require("gulp"); const gulp = require("gulp");
const path = require("path"); const path = require("path");
const cpx = require("cpx");
const fs = require("fs-extra"); const fs = require("fs-extra");
const paths = require("../paths"); const paths = require("../paths");
@@ -12,9 +13,7 @@ const polyPath = (...parts) => path.resolve(paths.polymer_dir, ...parts);
const copyFileDir = (fromFile, toDir) => const copyFileDir = (fromFile, toDir) =>
fs.copySync(fromFile, path.join(toDir, path.basename(fromFile))); fs.copySync(fromFile, path.join(toDir, path.basename(fromFile)));
const genStaticPath = const genStaticPath = (staticDir) => (...parts) =>
(staticDir) =>
(...parts) =>
path.resolve(staticDir, ...parts); path.resolve(staticDir, ...parts);
function copyTranslations(staticDir) { function copyTranslations(staticDir) {
@@ -22,18 +21,11 @@ function copyTranslations(staticDir) {
// Translation output // Translation output
fs.copySync( fs.copySync(
polyPath("build/translations/output"), polyPath("build-translations/output"),
staticPath("translations") staticPath("translations")
); );
} }
function copyLocaleData(staticDir) {
const staticPath = genStaticPath(staticDir);
// Locale data output
fs.copySync(polyPath("build/locale-data"), staticPath("locale-data"));
}
function copyMdiIcons(staticDir) { function copyMdiIcons(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
@@ -70,12 +62,9 @@ function copyLoaderJS(staticDir) {
function copyFonts(staticDir) { function copyFonts(staticDir) {
const staticPath = genStaticPath(staticDir); const staticPath = genStaticPath(staticDir);
// Local fonts // Local fonts
fs.copySync( cpx.copySync(
npmPath("roboto-fontface/fonts/roboto/"), npmPath("roboto-fontface/fonts/roboto/*.woff2"),
staticPath("fonts/roboto/"), staticPath("fonts/roboto")
{
filter: (src) => !src.includes(".") || src.endsWith(".woff2"),
}
); );
} }
@@ -91,11 +80,6 @@ function copyMapPanel(staticDir) {
); );
} }
gulp.task("copy-locale-data", async () => {
const staticDir = paths.app_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-translations-app", async () => { gulp.task("copy-translations-app", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
copyTranslations(staticDir); copyTranslations(staticDir);
@@ -106,11 +90,6 @@ gulp.task("copy-translations-supervisor", async () => {
copyTranslations(staticDir); copyTranslations(staticDir);
}); });
gulp.task("copy-locale-data-supervisor", async () => {
const staticDir = paths.hassio_output_static;
copyLocaleData(staticDir);
});
gulp.task("copy-static-app", async () => { gulp.task("copy-static-app", async () => {
const staticDir = paths.app_output_static; const staticDir = paths.app_output_static;
// Basic static files // Basic static files
@@ -120,7 +99,6 @@ gulp.task("copy-static-app", async () => {
copyPolyfills(staticDir); copyPolyfills(staticDir);
copyFonts(staticDir); copyFonts(staticDir);
copyTranslations(staticDir); copyTranslations(staticDir);
copyLocaleData(staticDir);
copyMdiIcons(staticDir); copyMdiIcons(staticDir);
// Panel assets // Panel assets
@@ -141,7 +119,6 @@ gulp.task("copy-static-demo", async () => {
copyMapPanel(paths.demo_output_static); copyMapPanel(paths.demo_output_static);
copyFonts(paths.demo_output_static); copyFonts(paths.demo_output_static);
copyTranslations(paths.demo_output_static); copyTranslations(paths.demo_output_static);
copyLocaleData(paths.demo_output_static);
copyMdiIcons(paths.demo_output_static); copyMdiIcons(paths.demo_output_static);
}); });
@@ -156,7 +133,6 @@ gulp.task("copy-static-cast", async () => {
copyMapPanel(paths.cast_output_static); copyMapPanel(paths.cast_output_static);
copyFonts(paths.cast_output_static); copyFonts(paths.cast_output_static);
copyTranslations(paths.cast_output_static); copyTranslations(paths.cast_output_static);
copyLocaleData(paths.cast_output_static);
copyMdiIcons(paths.cast_output_static); copyMdiIcons(paths.cast_output_static);
}); });
@@ -172,6 +148,5 @@ gulp.task("copy-static-gallery", async () => {
copyMapPanel(paths.gallery_output_static); copyMapPanel(paths.gallery_output_static);
copyFonts(paths.gallery_output_static); copyFonts(paths.gallery_output_static);
copyTranslations(paths.gallery_output_static); copyTranslations(paths.gallery_output_static);
copyLocaleData(paths.gallery_output_static);
copyMdiIcons(paths.gallery_output_static); copyMdiIcons(paths.gallery_output_static);
}); });

View File

@@ -1,6 +1,9 @@
const gulp = require("gulp"); const gulp = require("gulp");
const fs = require("fs");
const path = require("path");
const env = require("../env"); const env = require("../env");
const paths = require("../paths");
require("./clean.js"); require("./clean.js");
require("./gen-icons-json.js"); require("./gen-icons-json.js");
@@ -21,8 +24,6 @@ gulp.task(
"gen-index-hassio-dev", "gen-index-hassio-dev",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio" env.useRollup() ? "rollup-watch-hassio" : "webpack-watch-hassio"
) )
); );
@@ -37,8 +38,6 @@ gulp.task(
"gen-icons-json", "gen-icons-json",
"build-supervisor-translations", "build-supervisor-translations",
"copy-translations-supervisor", "copy-translations-supervisor",
"build-locale-data",
"copy-locale-data-supervisor",
env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio", env.useRollup() ? "rollup-prod-hassio" : "webpack-prod-hassio",
"gen-index-hassio-prod", "gen-index-hassio-prod",
...// Don't compress running tests ...// Don't compress running tests

View File

@@ -1,74 +0,0 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const del = require("del");
const path = require("path");
const gulp = require("gulp");
const fs = require("fs");
const paths = require("../paths");
const outDir = "build/locale-data";
gulp.task("clean-locale-data", () => del([outDir]));
gulp.task("ensure-locale-data-build-dir", (done) => {
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
done();
});
const modules = {
"intl-relativetimeformat": "RelativeTimeFormat",
"intl-datetimeformat": "DateTimeFormat",
"intl-numberformat": "NumberFormat",
};
gulp.task("create-locale-data", (done) => {
const translationMeta = JSON.parse(
fs.readFileSync(
path.join(paths.translations_src, "translationMetadata.json")
)
);
Object.entries(modules).forEach(([module, className]) => {
Object.keys(translationMeta).forEach((lang) => {
try {
const localeData = String(
fs.readFileSync(
require.resolve(`@formatjs/${module}/locale-data/${lang}.js`)
)
)
.replace(
new RegExp(
`\\/\\*\\s*@generated\\s*\\*\\/\\s*\\/\\/\\s*prettier-ignore\\s*if\\s*\\(Intl\\.${className}\\s*&&\\s*typeof\\s*Intl\\.${className}\\.__addLocaleData\\s*===\\s*'function'\\)\\s*{\\s*Intl\\.${className}\\.__addLocaleData\\(`,
"im"
),
""
)
.replace(/\)\s*}/im, "");
// make sure we have valid JSON
JSON.parse(localeData);
if (!fs.existsSync(path.join(outDir, module))) {
fs.mkdirSync(path.join(outDir, module), { recursive: true });
}
fs.writeFileSync(
path.join(outDir, `${module}/${lang}.json`),
localeData
);
} catch (e) {
if (e.code !== "MODULE_NOT_FOUND") {
throw e;
}
}
});
done();
});
});
gulp.task(
"build-locale-data",
gulp.series(
"clean-locale-data",
"ensure-locale-data-build-dir",
"create-locale-data"
)
);

View File

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

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// Tasks to run webpack. // Tasks to run webpack.
const fs = require("fs"); const fs = require("fs");
const gulp = require("gulp"); const gulp = require("gulp");
@@ -20,9 +19,7 @@ const bothBuilds = (createConfigFunc, params) => [
createConfigFunc({ ...params, latestBuild: false }), createConfigFunc({ ...params, latestBuild: false }),
]; ];
const isWsl = const isWsl = fs
fs.existsSync("/proc/version") &&
fs
.readFileSync("/proc/version", "utf-8") .readFileSync("/proc/version", "utf-8")
.toLocaleLowerCase() .toLocaleLowerCase()
.includes("microsoft"); .includes("microsoft");
@@ -35,29 +32,26 @@ const isWsl =
* listenHost?: string * listenHost?: string
* }} * }}
*/ */
const runDevServer = async ({ const runDevServer = ({
compiler, compiler,
contentBase, contentBase,
port, port,
listenHost = "localhost", listenHost = "localhost",
}) => { }) =>
const server = new WebpackDevServer( new WebpackDevServer(compiler, {
{
open: true, open: true,
host: listenHost, watchContentBase: true,
port, contentBase,
static: { }).listen(port, listenHost, function (err) {
directory: contentBase, if (err) {
watch: true, throw err;
}, }
},
compiler
);
await server.start();
// Server listening // Server listening
log("[webpack-dev-server]", `Project is running at http://localhost:${port}`); log(
}; "[webpack-dev-server]",
`Project is running at http://localhost:${port}`
);
});
const doneHandler = (done) => (err, stats) => { const doneHandler = (done) => (err, stats) => {
if (err) { if (err) {
@@ -69,7 +63,6 @@ const doneHandler = (done) => (err, stats) => {
} }
if (stats.hasErrors() || stats.hasWarnings()) { if (stats.hasErrors() || stats.hasWarnings()) {
// eslint-disable-next-line no-console
console.log(stats.toString("minimal")); console.log(stats.toString("minimal"));
} }
@@ -91,14 +84,13 @@ const prodBuild = (conf) =>
gulp.task("webpack-watch-app", () => { gulp.task("webpack-watch-app", () => {
// This command will run forever because we don't close compiler // This command will run forever because we don't close compiler
webpack( webpack(createAppConfig({ isProdBuild: false, latestBuild: true })).watch(
process.env.ES5 { ignored: /build-translations/, poll: isWsl },
? bothBuilds(createAppConfig, { isProdBuild: false }) doneHandler()
: createAppConfig({ isProdBuild: false, latestBuild: true }) );
).watch({ poll: isWsl }, doneHandler());
gulp.watch( gulp.watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
gulp.series("create-translations", "copy-translations-app") gulp.series("build-translations", "copy-translations-app")
); );
}); });
@@ -110,13 +102,13 @@ gulp.task("webpack-prod-app", () =>
) )
); );
gulp.task("webpack-dev-server-demo", () => gulp.task("webpack-dev-server-demo", () => {
runDevServer({ runDevServer({
compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })), compiler: webpack(bothBuilds(createDemoConfig, { isProdBuild: false })),
contentBase: paths.demo_output_root, contentBase: paths.demo_output_root,
port: 8090, port: 8090,
}) });
); });
gulp.task("webpack-prod-demo", () => gulp.task("webpack-prod-demo", () =>
prodBuild( prodBuild(
@@ -126,15 +118,15 @@ gulp.task("webpack-prod-demo", () =>
) )
); );
gulp.task("webpack-dev-server-cast", () => gulp.task("webpack-dev-server-cast", () => {
runDevServer({ runDevServer({
compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })), compiler: webpack(bothBuilds(createCastConfig, { isProdBuild: false })),
contentBase: paths.cast_output_root, contentBase: paths.cast_output_root,
port: 8080, port: 8080,
// Accessible from the network, because that's how Cast hits it. // Accessible from the network, because that's how Cast hits it.
listenHost: "0.0.0.0", listenHost: "0.0.0.0",
}) });
); });
gulp.task("webpack-prod-cast", () => gulp.task("webpack-prod-cast", () =>
prodBuild( prodBuild(
@@ -151,7 +143,7 @@ gulp.task("webpack-watch-hassio", () => {
isProdBuild: false, isProdBuild: false,
latestBuild: true, latestBuild: true,
}) })
).watch({ ignored: /build/, poll: isWsl }, doneHandler()); ).watch({ ignored: /build-translations/, poll: isWsl }, doneHandler());
gulp.watch( gulp.watch(
path.join(paths.translations_src, "en.json"), path.join(paths.translations_src, "en.json"),
@@ -167,14 +159,14 @@ gulp.task("webpack-prod-hassio", () =>
) )
); );
gulp.task("webpack-dev-server-gallery", () => gulp.task("webpack-dev-server-gallery", () => {
runDevServer({ runDevServer({
// We don't use the es5 build, but the dev server will fuck up the publicPath if we don't // We don't use the es5 build, but the dev server will fuck up the publicPath if we don't
compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })), compiler: webpack(bothBuilds(createGalleryConfig, { isProdBuild: false })),
contentBase: paths.gallery_output_root, contentBase: paths.gallery_output_root,
port: 8100, port: 8100,
}) });
); });
gulp.task("webpack-prod-gallery", () => gulp.task("webpack-prod-gallery", () =>
prodBuild( prodBuild(

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,6 @@ const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const paths = require("./paths.js"); const paths = require("./paths.js");
const bundle = require("./bundle.js"); const bundle = require("./bundle.js");
const log = require("fancy-log"); const log = require("fancy-log");
const WebpackBar = require("webpackbar");
class LogStartCompilePlugin { class LogStartCompilePlugin {
ignoredFirst = false; ignoredFirst = false;
@@ -50,16 +49,12 @@ const createWebpackConfig = ({
test: /\.m?js$|\.ts$/, test: /\.m?js$|\.ts$/,
use: { use: {
loader: "babel-loader", loader: "babel-loader",
options: { options: bundle.babelOptions({ latestBuild }),
...bundle.babelOptions({ latestBuild }),
cacheDirectory: !isProdBuild,
cacheCompression: false,
},
}, },
}, },
{ {
test: /\.css$/, test: /\.css$/,
type: "asset/source", use: "raw-loader",
}, },
], ],
}, },
@@ -71,11 +66,8 @@ const createWebpackConfig = ({
terserOptions: bundle.terserOptions(latestBuild), terserOptions: bundle.terserOptions(latestBuild),
}), }),
], ],
moduleIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
chunkIds: isProdBuild && !isStatsBuild ? "deterministic" : "named",
}, },
plugins: [ plugins: [
new WebpackBar({ fancy: !isProdBuild }),
new WebpackManifestPlugin({ new WebpackManifestPlugin({
// Only include the JS of entrypoints // Only include the JS of entrypoints
filter: (file) => file.isInitial && !file.name.endsWith(".map"), filter: (file) => file.isInitial && !file.name.endsWith(".map"),
@@ -120,6 +112,16 @@ const createWebpackConfig = ({
new RegExp(bundle.emptyPackages({ latestBuild }).join("|")), new RegExp(bundle.emptyPackages({ latestBuild }).join("|")),
path.resolve(paths.polymer_dir, "src/util/empty.js") 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(
path.resolve(
paths.polymer_dir,
"src/resources/lit-virtualizer/lib/uni-virtualizer/lib/polyfillLoaders/EventTarget.js"
)
),
path.resolve(paths.polymer_dir, "src/resources/EventTarget-ponyfill.js")
),
!isProdBuild && new LogStartCompilePlugin(), !isProdBuild && new LogStartCompilePlugin(),
].filter(Boolean), ].filter(Boolean),
resolve: { resolve: {
@@ -127,33 +129,25 @@ const createWebpackConfig = ({
alias: { alias: {
"lit/decorators$": "lit/decorators.js", "lit/decorators$": "lit/decorators.js",
"lit/directive$": "lit/directive.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", "lit/polyfill-support$": "lit/polyfill-support.js",
}, },
}, },
output: { output: {
filename: ({ chunk }) => { filename: ({ chunk }) => {
if (!isProdBuild || isStatsBuild || dontHash.has(chunk.name)) { if (!isProdBuild || dontHash.has(chunk.name)) {
return `${chunk.name}.js`; return `${chunk.name}.js`;
} }
return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`; return `${chunk.name}.${chunk.hash.substr(0, 8)}.js`;
}, },
chunkFilename: chunkFilename:
isProdBuild && !isStatsBuild ? "[chunkhash:8].js" : "[id].chunk.js", isProdBuild && !isStatsBuild
? "chunk.[chunkhash].js"
: "[name].chunk.js",
path: outputPath, path: outputPath,
publicPath, publicPath,
// To silence warning in worker plugin // To silence warning in worker plugin
globalObject: "self", globalObject: "self",
}, },
experiments: {
topLevelAwait: true,
},
}; };
}; };

View File

@@ -139,7 +139,7 @@
Your authentication credentials or Home Assistant url are never sent Your authentication credentials or Home Assistant url are never sent
to the Cloud. You can validate this behavior in to the Cloud. You can validate this behavior in
<a <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" target="_blank"
>the source code</a >the source code</a
>. >.

View File

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

View File

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

View File

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

View File

@@ -148,14 +148,14 @@ export class HcMain extends HassElement {
expires_in: 0, expires_in: 0,
}), }),
}); });
} catch (err: any) { } catch (err) {
this._error = this._getErrorMessage(err); this._error = this._getErrorMessage(err);
return; return;
} }
let connection; let connection;
try { try {
connection = await createConnection({ auth }); connection = await createConnection({ auth });
} catch (err: any) { } catch (err) {
this._error = this._getErrorMessage(err); this._error = this._getErrorMessage(err);
return; return;
} }
@@ -193,7 +193,7 @@ export class HcMain extends HassElement {
this._unsubLovelace = llColl.subscribe((lovelaceConfig) => this._unsubLovelace = llColl.subscribe((lovelaceConfig) =>
this._handleNewLovelaceConfig(lovelaceConfig) this._handleNewLovelaceConfig(lovelaceConfig)
); );
} catch (err: any) { } catch (err) {
// eslint-disable-next-line // eslint-disable-next-line
console.log("Error fetching Lovelace configuration", err, msg); console.log("Error fetching Lovelace configuration", err, msg);
// Generate a Lovelace config. // 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", type: "thermostat",
entity: "climate.upstairs", entity: "climate.upstairs",
@@ -118,7 +113,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { 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)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -200,7 +196,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { 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)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -280,7 +277,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { 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)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {
@@ -317,7 +315,8 @@ export const demoLovelaceArsaboo: DemoConfig["lovelace"] = (localize) => ({
on: "/assets/arsaboo/icons/light_bulb_on.png", on: "/assets/arsaboo/icons/light_bulb_on.png",
}, },
state_filter: { 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)", off: "brightness(80%) saturate(0.8)",
}, },
style: { style: {

View File

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

View File

@@ -980,7 +980,8 @@ export const demoEntitiesTeachingbirds: DemoConfig["entities"] = () =>
icon: "mdi:account-off", icon: "mdi:account-off",
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
templates: { 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: icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n", "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", icon: "mdi:account-multiple-minus",
custom_ui_state_card: "state-card-custom-ui", custom_ui_state_card: "state-card-custom-ui",
templates: { 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: icon_color:
"if (state === 'on') return 'rgb(56, 150, 56)'; else if (state === 'off') return 'rgb(249, 251, 255)';\n", "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; @property({ attribute: false }) public hass!: MockHomeAssistant;
@state() private _switching = false; @state() private _switching?: boolean;
private _hidden = localStorage.hide_demo_card; private _hidden = localStorage.hide_demo_card;
@@ -27,7 +27,12 @@ export class HADemoCard extends LitElement implements LovelaceCard {
return this._hidden ? 0 : 2; 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 { protected render(): TemplateResult {
if (this._hidden) { if (this._hidden) {
@@ -44,7 +49,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
(conf) => html` (conf) => html`
${conf.name} ${conf.name}
<small> <small>
<a target="_blank" href=${conf.authorUrl}> <a target="_blank" href="${conf.authorUrl}">
${this.hass.localize( ${this.hass.localize(
"ui.panel.page-demo.cards.demo.demo_by", "ui.panel.page-demo.cards.demo.demo_by",
"name", "name",
@@ -94,7 +99,7 @@ export class HADemoCard extends LitElement implements LovelaceCard {
this._switching = true; this._switching = true;
try { try {
await setDemoConfig(this.hass, this.lovelace!, index); await setDemoConfig(this.hass, this.lovelace!, index);
} catch (err: any) { } catch (err) {
alert("Failed to switch config :-("); alert("Failed to switch config :-(");
} finally { } finally {
this._switching = false; 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/ha-style";
import "../../src/resources/roboto"; import "../../src/resources/roboto";
import "../../src/resources/safari-14-attachshadow-patch"; 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 { mockSystemLog } from "./stubs/system_log";
import { mockTemplate } from "./stubs/template"; import { mockTemplate } from "./stubs/template";
import { mockTranslations } from "./stubs/translations"; import { mockTranslations } from "./stubs/translations";
import { mockEnergy } from "./stubs/energy";
import { mockConfig } from "./stubs/config";
import { energyEntities } from "./stubs/entities";
class HaDemo extends HomeAssistantAppEl { class HaDemo extends HomeAssistantAppEl {
protected async _initializeHass() { protected async _initializeHass() {
@@ -50,12 +47,8 @@ class HaDemo extends HomeAssistantAppEl {
mockEvents(hass); mockEvents(hass);
mockMediaPlayer(hass); mockMediaPlayer(hass);
mockFrontend(hass); mockFrontend(hass);
mockEnergy(hass);
mockConfig(hass);
mockPersistentNotification(hass); mockPersistentNotification(hass);
hass.addEntities(energyEntities());
// Once config is loaded AND localize, set entities and apply theme. // Once config is loaded AND localize, set entities and apply theme.
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([selectedDemoConfig, localizePromise]).then(
([conf, localize]) => { ([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 { HassEntity } from "home-assistant-js-websocket";
import { StatisticValue } from "../../../src/data/history";
import { MockHomeAssistant } from "../../../src/fake_data/provide_hass"; import { MockHomeAssistant } from "../../../src/fake_data/provide_hass";
interface HistoryQueryParams { interface HistoryQueryParams {
@@ -66,219 +64,17 @@ const generateHistory = (state, deltas) => {
const incrementalUnits = ["clients", "queries", "ads"]; 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) => { export const mockHistory = (mockHass: MockHomeAssistant) => {
mockHass.mockAPI( mockHass.mockAPI(
new RegExp("history/period/.+"), new RegExp("history/period/.+"),
(hass, _method, path, _parameters) => { (
hass,
// @ts-ignore
method,
path,
// @ts-ignore
parameters
) => {
const params = parseQuery<HistoryQueryParams>(path.split("?")[1]); const params = parseQuery<HistoryQueryParams>(path.split("?")[1]);
const entities = params.filter_entity_id.split(","); const entities = params.filter_entity_id.split(",");
@@ -299,7 +95,7 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
const numberState = Number(state.state); const numberState = Number(state.state);
if (isNaN(numberState)) { if (isNaN(numberState)) {
// eslint-disable-next-line no-console // eslint-disable-next-line
console.log( console.log(
"Ignoring state with unparsable state but with a unit", "Ignoring state with unparsable state but with a unit",
entityId, entityId,
@@ -344,40 +140,4 @@ export const mockHistory = (mockHass: MockHomeAssistant) => {
return results; 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> localizePromise: Promise<LocalizeFunc>
) => { ) => {
hass.mockWS("lovelace/config", () => hass.mockWS("lovelace/config", () =>
Promise.all([selectedDemoConfig, localizePromise]).then( Promise.all([
([config, localize]) => config.lovelace(localize) selectedDemoConfig,
) localizePromise,
]).then(([config, localize]) => config.lovelace(localize))
); );
hass.mockWS("lovelace/config/save", () => Promise.resolve()); hass.mockWS("lovelace/config/save", () => Promise.resolve());
@@ -23,9 +24,9 @@ customElements.whenDefined("hui-view").then(() => {
// eslint-disable-next-line // eslint-disable-next-line
const HUIView = customElements.get("hui-view"); const HUIView = customElements.get("hui-view");
// Patch HUI-VIEW to make the lovelace object available to the demo card // 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); const el = oldCreateCard.call(this, config);
if (el.tagName === "HA-DEMO-CARD") { if (el.tagName === "HA-DEMO-CARD") {
(el as HADemoCard).lovelace = this.lovelace; (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." }, 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!({ onChange!({
result: msg.template, result: msg.template,
listeners: { all: false, domains: [], entities: [], time: false }, 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 { html, css, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-card"; 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 { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/trace/hat-script-graph"; import "../../../src/components/trace/hat-script-graph";
import "../../../src/components/trace/hat-trace-timeline"; import "../../../src/components/trace/hat-trace-timeline";
import { customElement, property, state } from "lit/decorators";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
import { DemoTrace } from "../data/traces/types"; import { DemoTrace } from "../data/traces/types";
import { basicTrace } from "../data/traces/basic_trace"; import { basicTrace } from "../data/traces/basic_trace";
import { motionLightTrace } from "../data/traces/motion-light-trace"; import { motionLightTrace } from "../data/traces/motion-light-trace";
import { customElement, property, state } from "lit/decorators";
const traces: DemoTrace[] = [basicTrace, motionLightTrace]; const traces: DemoTrace[] = [basicTrace, motionLightTrace];

View File

@@ -1,156 +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">
<div class="card-content">
${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>
`
)}
</div>
</ha-card>
`;
}
static get styles() {
return css`
ha-card {
max-width: 600px;
margin: 24px auto;
}
ha-alert {
display: block;
margin: 24px 0;
}
.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

@@ -1,212 +0,0 @@
/* eslint-disable lit/no-template-arrow */
import { LitElement, TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators";
import "../../../src/components/ha-form/ha-form";
import "../../../src/components/ha-card";
import { applyThemesOnElement } from "../../../src/common/dom/apply_themes_on_element";
import type { HaFormSchema } from "../../../src/components/ha-form/ha-form";
const SCHEMAS: {
title: string;
translations?: Record<string, string>;
error?: Record<string, string>;
schema: HaFormSchema[];
}[] = [
{
title: "Authentication",
translations: {
username: "Username",
password: "Password",
invalid_login: "Invalid login",
},
error: {
base: "invalid_login",
},
schema: [
{
type: "string",
name: "username",
required: true,
},
{
type: "string",
name: "password",
required: true,
},
],
},
{
title: "One of each",
schema: [
{
type: "constant",
value: "Constant Value",
name: "constant",
required: true,
},
{
type: "boolean",
name: "bool",
optional: true,
default: false,
},
{
type: "integer",
name: "int",
optional: true,
default: 10,
},
{
type: "string",
name: "string",
optional: true,
default: "Default",
},
{
type: "select",
options: [
["default", "default"],
["other", "other"],
],
name: "select",
optional: true,
default: "default",
},
{
type: "multi_select",
options: {
default: "Default",
other: "Other",
},
name: "multi",
optional: true,
default: ["default"],
},
],
},
{
title: "Multi select",
schema: [
{
type: "multi_select",
options: {
default: "Default",
other: "Other",
},
name: "multi",
optional: true,
default: ["default"],
},
{
type: "multi_select",
options: {
default: "Default",
other: "Other",
uno: "mas",
one: "more",
and: "another_one",
option: "1000",
},
name: "multi",
optional: true,
default: ["default"],
},
],
},
];
@customElement("demo-ha-form")
class DemoHaForm extends LitElement {
private lightModeData: any = [];
private darkModeData: any = [];
protected render(): TemplateResult {
return html`
${SCHEMAS.map((info, idx) => {
const translations = info.translations || {};
const computeLabel = (schema) =>
translations[schema.name] || schema.name;
const computeError = (error) => translations[error] || error;
return [
[this.lightModeData, "light"],
[this.darkModeData, "dark"],
].map(
([data, type]) => html`
<div class="row" data-type=${type}>
<ha-card .header=${info.title}>
<div class="card-content">
<ha-form
.data=${data[idx]}
.schema=${info.schema}
.error=${info.error}
.computeError=${computeError}
.computeLabel=${computeLabel}
@value-changed=${(e) => {
data[idx] = e.detail.value;
this.requestUpdate();
}}
></ha-form>
</div>
</ha-card>
<pre>${JSON.stringify(data[idx], undefined, 2)}</pre>
</div>
`
);
})}
`;
}
firstUpdated(changedProps) {
super.firstUpdated(changedProps);
this.shadowRoot!.querySelectorAll("[data-type=dark]").forEach((el) => {
applyThemesOnElement(
el,
{
default_theme: "default",
default_dark_theme: "default",
themes: {},
darkMode: false,
},
"default",
{ dark: true }
);
});
}
static styles = css`
.row {
margin: 0 auto;
max-width: 800px;
display: flex;
padding: 50px;
background-color: var(--primary-background-color);
}
ha-card {
width: 100%;
max-width: 384px;
}
pre {
width: 400px;
margin: 0 16px;
overflow: auto;
color: var(--primary-text-color);
}
@media only screen and (max-width: 800px) {
.row {
flex-direction: column;
}
pre {
margin: 16px 0;
}
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"demo-ha-form": DemoHaForm;
}
}

View File

@@ -2,8 +2,6 @@ import { html, css, LitElement, TemplateResult } from "lit";
import "../../../src/components/ha-formfield"; import "../../../src/components/ha-formfield";
import "../../../src/components/ha-switch"; 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 { IntegrationManifest } from "../../../src/data/integration";
import { provideHass } from "../../../src/fake_data/provide_hass"; import { provideHass } from "../../../src/fake_data/provide_hass";
@@ -17,6 +15,8 @@ import type {
} from "../../../src/panels/config/integrations/ha-config-integrations"; } from "../../../src/panels/config/integrations/ha-config-integrations";
import { DeviceRegistryEntry } from "../../../src/data/device_registry"; import { DeviceRegistryEntry } from "../../../src/data/device_registry";
import { EntityRegistryEntry } from "../../../src/data/entity_registry"; import { EntityRegistryEntry } from "../../../src/data/entity_registry";
import { classMap } from "lit/directives/class-map";
import { customElement, property, state } from "lit/decorators";
const createConfigEntry = ( const createConfigEntry = (
title: string, title: string,

View File

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

View File

@@ -172,14 +172,6 @@ class HaGallery extends PolymerElement {
this.$.notifications.showDialog({ message: ev.detail.message }) 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) => { this.addEventListener("hass-more-info", (ev) => {
if (ev.detail.entityId) { if (ev.detail.entityId) {
this.$.notifications.showDialog({ this.$.notifications.showDialog({

View File

@@ -4,7 +4,6 @@ import { property } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { import {
HassioAddonInfo, HassioAddonInfo,
@@ -33,7 +32,7 @@ class HassioAddonRepositoryEl extends LitElement {
return filterAndSort(addons, filter); return filterAndSort(addons, filter);
} }
return addons.sort((a, b) => return addons.sort((a, b) =>
caseInsensitiveStringCompare(a.name, b.name) a.name.toUpperCase() < b.name.toUpperCase() ? -1 : 1
); );
} }
); );

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,7 +23,6 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { navigate } from "../../../../src/common/navigate"; import { navigate } from "../../../../src/common/navigate";
import "../../../../src/components/buttons/ha-call-api-button"; import "../../../../src/components/buttons/ha-call-api-button";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-card"; import "../../../../src/components/ha-card";
import "../../../../src/components/ha-label-badge"; import "../../../../src/components/ha-label-badge";
import "../../../../src/components/ha-markdown"; import "../../../../src/components/ha-markdown";
@@ -123,18 +122,18 @@ class HassioAddonInfo extends LitElement {
<div class="card-content"> <div class="card-content">
<hassio-card-content <hassio-card-content
.hass=${this.hass} .hass=${this.hass}
.title=${this.supervisor.localize( .title="${this.supervisor.localize(
"addon.dashboard.new_update_available", "addon.dashboard.new_update_available",
"name", "name",
this.addon.name, this.addon.name,
"version", "version",
this.addon.version_latest this.addon.version_latest
)} )}"
.description=${this.supervisor.localize( .description="${this.supervisor.localize(
"common.running_version", "common.running_version",
"version", "version",
this.addon.version this.addon.version
)} )}"
icon=${mdiArrowUpBoldCircle} icon=${mdiArrowUpBoldCircle}
iconClass="update" iconClass="update"
></hassio-card-content> ></hassio-card-content>
@@ -144,14 +143,14 @@ class HassioAddonInfo extends LitElement {
this.addon.arch this.addon.arch
) )
? html` ? html`
<ha-alert alert-type="warning"> <p class="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_arch" "addon.dashboard.not_available_arch"
)} )}
</ha-alert> </p>
` `
: html` : html`
<ha-alert alert-type="warning"> <p class="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_arch", "addon.dashboard.not_available_arch",
"core_version_installed", "core_version_installed",
@@ -159,7 +158,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed", "core_version_needed",
addonStoreInfo.homeassistant addonStoreInfo.homeassistant
)} )}
</ha-alert> </p>
` `
: ""} : ""}
</div> </div>
@@ -254,7 +253,7 @@ class HassioAddonInfo extends LitElement {
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.visit_addon_page", "addon.dashboard.visit_addon_page",
"name", "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 >${this.addon.name}</a
>` >`
)} )}
@@ -297,11 +296,10 @@ class HassioAddonInfo extends LitElement {
})} })}
@click=${this._showMoreInfo} @click=${this._showMoreInfo}
id="rating" id="rating"
.value=${this.addon.rating}
label="rating" label="rating"
description="" description=""
> ></ha-label-badge>
${this.addon.rating}
</ha-label-badge>
${this.addon.host_network ${this.addon.host_network
? html` ? html`
<ha-label-badge <ha-label-badge
@@ -438,10 +436,10 @@ class HassioAddonInfo extends LitElement {
${this.addon.version ${this.addon.version
? html` ? html`
<div <div
class=${classMap({ class="${classMap({
"addon-options": true, "addon-options": true,
started: this.addon.state === "started", started: this.addon.state === "started",
})} })}"
> >
<ha-settings-row ?three-line=${this.narrow}> <ha-settings-row ?three-line=${this.narrow}>
<span slot="heading"> <span slot="heading">
@@ -571,23 +569,21 @@ class HassioAddonInfo extends LitElement {
: ""} : ""}
</div> </div>
</div> </div>
${this._error ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${!this.addon.version && addonStoreInfo && !this.addon.available ${!this.addon.version && addonStoreInfo && !this.addon.available
? !addonArchIsSupported( ? !addonArchIsSupported(
this.supervisor.info.supported_arch, this.supervisor.info.supported_arch,
this.addon.arch this.addon.arch
) )
? html` ? html`
<ha-alert alert-type="warning"> <p class="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_arch" "addon.dashboard.not_available_arch"
)} )}
</ha-alert> </p>
` `
: html` : html`
<ha-alert alert-type="warning"> <p class="warning">
${this.supervisor.localize( ${this.supervisor.localize(
"addon.dashboard.not_available_version", "addon.dashboard.not_available_version",
"core_version_installed", "core_version_installed",
@@ -595,7 +591,7 @@ class HassioAddonInfo extends LitElement {
"core_version_needed", "core_version_needed",
addonStoreInfo!.homeassistant addonStoreInfo!.homeassistant
)} )}
</ha-alert> </p>
` `
: ""} : ""}
</div> </div>
@@ -797,7 +793,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -819,7 +815,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -841,7 +837,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -863,7 +859,7 @@ class HassioAddonInfo extends LitElement {
path: "security", path: "security",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -885,7 +881,7 @@ class HassioAddonInfo extends LitElement {
path: "option", path: "option",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"addon.failed_to_save", "addon.failed_to_save",
"error", "error",
@@ -896,24 +892,15 @@ class HassioAddonInfo extends LitElement {
private async _openChangelog(): Promise<void> { private async _openChangelog(): Promise<void> {
try { try {
let content = await fetchHassioAddonChangelog(this.hass, this.addon.slug); const content = await fetchHassioAddonChangelog(
if ( this.hass,
content.includes(`# ${this.addon.version}`) && this.addon.slug
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;
}
}
showHassioMarkdownDialog(this, { showHassioMarkdownDialog(this, {
title: this.supervisor.localize("addon.dashboard.changelog"), title: this.supervisor.localize("addon.dashboard.changelog"),
content, content,
}); });
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"addon.dashboard.action_error.get_changelog" "addon.dashboard.action_error.get_changelog"
@@ -935,7 +922,7 @@ class HassioAddonInfo extends LitElement {
path: "install", path: "install",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.install"), title: this.supervisor.localize("addon.dashboard.action_error.install"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -956,7 +943,7 @@ class HassioAddonInfo extends LitElement {
path: "stop", path: "stop",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.stop"), title: this.supervisor.localize("addon.dashboard.action_error.stop"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -977,7 +964,7 @@ class HassioAddonInfo extends LitElement {
path: "stop", path: "stop",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.restart"), title: this.supervisor.localize("addon.dashboard.action_error.restart"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -990,8 +977,9 @@ class HassioAddonInfo extends LitElement {
showDialogSupervisorUpdate(this, { showDialogSupervisorUpdate(this, {
supervisor: this.supervisor, supervisor: this.supervisor,
name: this.addon.name, name: this.addon.name,
slug: this.addon.slug,
version: this.addon.version_latest, version: this.addon.version_latest,
backupParams: { snapshotParams: {
name: `addon_${this.addon.slug}_${this.addon.version}`, name: `addon_${this.addon.slug}_${this.addon.version}`,
addons: [this.addon.slug], addons: [this.addon.slug],
homeassistant: false, homeassistant: false,
@@ -1036,7 +1024,7 @@ class HassioAddonInfo extends LitElement {
button.progress = false; button.progress = false;
return; return;
} }
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to validate addon configuration", title: "Failed to validate addon configuration",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -1054,7 +1042,7 @@ class HassioAddonInfo extends LitElement {
path: "start", path: "start",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("addon.dashboard.action_error.start"), title: this.supervisor.localize("addon.dashboard.action_error.start"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -1092,7 +1080,7 @@ class HassioAddonInfo extends LitElement {
path: "uninstall", path: "uninstall",
}; };
fireEvent(this, "hass-api-called", eventdata); fireEvent(this, "hass-api-called", eventdata);
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"addon.dashboard.action_error.uninstall" "addon.dashboard.action_error.uninstall"
@@ -1153,7 +1141,6 @@ class HassioAddonInfo extends LitElement {
margin-bottom: 16px; margin-bottom: 16px;
} }
img.logo { img.logo {
max-width: 100%;
max-height: 60px; max-height: 60px;
margin: 16px 0; margin: 16px 0;
display: block; display: block;
@@ -1163,10 +1150,10 @@ class HassioAddonInfo extends LitElement {
display: flex; display: flex;
} }
ha-svg-icon.running { ha-svg-icon.running {
color: var(--success-color); color: var(--paper-green-400);
} }
ha-svg-icon.stopped { ha-svg-icon.stopped {
color: var(--error-color); color: var(--google-red-300);
} }
ha-call-api-button { ha-call-api-button {
font-weight: 500; font-weight: 500;

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
import { mdiHelpCircle } from "@mdi/js"; import { mdiHelpCircle } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import "../../../src/components/ha-relative-time";
import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-svg-icon";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -18,6 +19,8 @@ class HassioCardContent extends LitElement {
@property() public topbarClass?: string; @property() public topbarClass?: string;
@property() public datetime?: string;
@property() public iconTitle?: string; @property() public iconTitle?: string;
@property() public iconClass?: string; @property() public iconClass?: string;
@@ -34,7 +37,7 @@ class HassioCardContent extends LitElement {
${this.iconImage ${this.iconImage
? html` ? html`
<div class="icon_image ${this.iconClass}"> <div class="icon_image ${this.iconClass}">
<img src=${this.iconImage} .title=${this.iconTitle} /> <img src="${this.iconImage}" .title=${this.iconTitle} />
<div></div> <div></div>
</div> </div>
` `
@@ -53,6 +56,15 @@ class HassioCardContent extends LitElement {
/* treat as available when undefined */ /* treat as available when undefined */
this.available === false ? " (Not available)" : "" this.available === false ? " (Not available)" : ""
} }
${this.datetime
? html`
<ha-relative-time
.hass=${this.hass}
class="addition"
.datetime=${this.datetime}
></ha-relative-time>
`
: undefined}
</div> </div>
</div> </div>
`; `;
@@ -68,14 +80,14 @@ class HassioCardContent extends LitElement {
color: var(--secondary-text-color); color: var(--secondary-text-color);
} }
ha-svg-icon.update { ha-svg-icon.update {
color: var(--warning-color); color: var(--paper-orange-400);
} }
ha-svg-icon.running, ha-svg-icon.running,
ha-svg-icon.installed { ha-svg-icon.installed {
color: var(--success-color); color: var(--paper-green-400);
} }
ha-svg-icon.hassupdate, ha-svg-icon.hassupdate,
ha-svg-icon.backup { ha-svg-icon.snapshot {
color: var(--paper-item-icon-color); color: var(--paper-item-icon-color);
} }
ha-svg-icon.not_available { ha-svg-icon.not_available {
@@ -94,6 +106,9 @@ class HassioCardContent extends LitElement {
height: 2.4em; height: 2.4em;
line-height: 1.2em; line-height: 1.2em;
} }
ha-relative-time {
display: block;
}
.icon_image img { .icon_image img {
max-height: 40px; max-height: 40px;
max-width: 40px; max-width: 40px;
@@ -107,7 +122,7 @@ class HassioCardContent extends LitElement {
} }
.dot { .dot {
position: absolute; position: absolute;
background-color: var(--warning-color); background-color: var(--paper-orange-400);
width: 12px; width: 12px;
height: 12px; height: 12px;
top: 8px; top: 8px;

View File

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

View File

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

View File

@@ -11,10 +11,10 @@ import "../../../src/components/ha-formfield";
import "../../../src/components/ha-radio"; import "../../../src/components/ha-radio";
import type { HaRadio } from "../../../src/components/ha-radio"; import type { HaRadio } from "../../../src/components/ha-radio";
import { import {
HassioFullBackupCreateParams, HassioFullSnapshotCreateParams,
HassioPartialBackupCreateParams, HassioPartialSnapshotCreateParams,
HassioBackupDetail, HassioSnapshotDetail,
} from "../../../src/data/hassio/backup"; } from "../../../src/data/hassio/snapshot";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { PolymerChangedEvent } from "../../../src/polymer-types"; import { PolymerChangedEvent } from "../../../src/polymer-types";
import { HomeAssistant } from "../../../src/types"; import { HomeAssistant } from "../../../src/types";
@@ -64,17 +64,17 @@ const _computeAddons = (addons): AddonCheckboxItem[] =>
})) }))
.sort((a, b) => (a.name > b.name ? 1 : -1)); .sort((a, b) => (a.name > b.name ? 1 : -1));
@customElement("supervisor-backup-content") @customElement("supervisor-snapshot-content")
export class SupervisorBackupContent extends LitElement { export class SupervisorSnapshotContent extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property() public localize?: LocalizeFunc; @property() public localize?: LocalizeFunc;
@property({ attribute: false }) public supervisor?: Supervisor; @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[]; @property({ attribute: false }) public folders?: CheckboxItem[];
@@ -82,35 +82,37 @@ export class SupervisorBackupContent extends LitElement {
@property({ type: Boolean }) public homeAssistant = false; @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({ type: Boolean }) public onboarding = false;
@property() public backupName = ""; @property() public snapshotName = "";
@property() public backupPassword = ""; @property() public snapshotPassword = "";
@property() public confirmBackupPassword = ""; @property() public confirmSnapshotPassword = "";
public willUpdate(changedProps) { public willUpdate(changedProps) {
super.willUpdate(changedProps); super.willUpdate(changedProps);
if (!this.hasUpdated) { if (!this.hasUpdated) {
this.folders = _computeFolders( this.folders = _computeFolders(
this.backup this.snapshot
? this.backup.folders ? this.snapshot.folders
: ["homeassistant", "ssl", "share", "media", "addons/local"] : ["homeassistant", "ssl", "share", "media", "addons/local"]
); );
this.addons = _computeAddons( 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.snapshotType = this.snapshot?.type || "full";
this.backupName = this.backup?.name || ""; this.snapshotName = this.snapshot?.name || "";
this.backupHasPassword = this.backup?.protected || false; this.snapshotHasPassword = this.snapshot?.protected || false;
} }
} }
private _localize = (string: string) => private _localize = (string: string) =>
this.supervisor?.localize(`backup.${string}`) || this.supervisor?.localize(`snapshot.${string}`) ||
this.localize!(`ui.panel.page-onboarding.restore.${string}`); this.localize!(`ui.panel.page-onboarding.restore.${string}`);
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -118,70 +120,72 @@ export class SupervisorBackupContent extends LitElement {
return html``; return html``;
} }
const foldersSection = const foldersSection =
this.backupType === "partial" ? this._getSection("folders") : undefined; this.snapshotType === "partial" ? this._getSection("folders") : undefined;
const addonsSection = const addonsSection =
this.backupType === "partial" ? this._getSection("addons") : undefined; this.snapshotType === "partial" ? this._getSection("addons") : undefined;
return html` return html`
${this.backup ${this.snapshot
? html`<div class="details"> ? html`<div class="details">
${this.backup.type === "full" ${this.snapshot.type === "full"
? this._localize("full_backup") ? this._localize("full_snapshot")
: this._localize("partial_backup")} : this._localize("partial_snapshot")}
(${Math.ceil(this.backup.size * 10) / 10 + " MB"})<br /> (${Math.ceil(this.snapshot.size * 10) / 10 + " MB"})<br />
${this.hass ${this.hass
? formatDateTime(new Date(this.backup.date), this.hass.locale) ? formatDateTime(new Date(this.snapshot.date), this.hass.locale)
: this.backup.date} : this.snapshot.date}
</div>` </div>`
: html`<paper-input : html`<paper-input
name="backupName" name="snapshotName"
.label=${this._localize("name")} .label=${this.supervisor?.localize("snapshot.name") || "Name"}
.value=${this.backupName} .value=${this.snapshotName}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input>`} </paper-input>`}
${!this.backup || this.backup.type === "full" ${!this.snapshot || this.snapshot.type === "full"
? html`<div class="sub-header"> ? html`<div class="sub-header">
${!this.backup ${!this.snapshot
? this._localize("type") ? this._localize("type")
: this._localize("select_type")} : this._localize("select_type")}
</div> </div>
<div class="backup-types"> <div class="snapshot-types">
<ha-formfield .label=${this._localize("full_backup")}> <ha-formfield .label=${this._localize("full_snapshot")}>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="full" value="full"
name="backupType" name="snapshotType"
.checked=${this.backupType === "full"} .checked=${this.snapshotType === "full"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
<ha-formfield .label=${this._localize("partial_backup")}> <ha-formfield .label=${this._localize("partial_snapshot")}>
<ha-radio <ha-radio
@change=${this._handleRadioValueChanged} @change=${this._handleRadioValueChanged}
value="partial" value="partial"
name="backupType" name="snapshotType"
.checked=${this.backupType === "partial"} .checked=${this.snapshotType === "partial"}
> >
</ha-radio> </ha-radio>
</ha-formfield> </ha-formfield>
</div>` </div>`
: ""} : ""}
${this.backupType === "partial" ${this.snapshotType === "partial"
? html`<div class="partial-picker"> ? html`<div class="partial-picker">
${this.backup && this.backup.homeassistant ${this.snapshot && this.snapshot.homeassistant
? html` ? html`
<ha-formfield <ha-formfield
.label=${html`<supervisor-formfield-label .label=${html`<supervisor-formfield-label
label="Home Assistant" label="Home Assistant"
.iconPath=${mdiHomeAssistant} .iconPath=${mdiHomeAssistant}
.version=${this.backup.homeassistant} .version=${this.snapshot.homeassistant}
> >
</supervisor-formfield-label>`} </supervisor-formfield-label>`}
> >
<ha-checkbox <ha-checkbox
.checked=${this.homeAssistant} .checked=${this.homeAssistant}
@click=${this.toggleHomeAssistant} @click=${() => {
this.homeAssistant = !this.homeAssistant;
}}
> >
</ha-checkbox> </ha-checkbox>
</ha-formfield> </ha-formfield>
@@ -229,38 +233,38 @@ export class SupervisorBackupContent extends LitElement {
: ""} : ""}
</div> ` </div> `
: ""} : ""}
${this.backupType === "partial" && ${this.snapshotType === "partial" &&
(!this.backup || this.backupHasPassword) (!this.snapshot || this.snapshotHasPassword)
? html`<hr />` ? html`<hr />`
: ""} : ""}
${!this.backup ${!this.snapshot
? html`<ha-formfield ? html`<ha-formfield
class="password" class="password"
.label=${this._localize("password_protection")} .label=${this._localize("password_protection")}
> >
<ha-checkbox <ha-checkbox
.checked=${this.backupHasPassword} .checked=${this.snapshotHasPassword}
@change=${this._toggleHasPassword} @change=${this._toggleHasPassword}
> >
</ha-checkbox> </ha-checkbox>
</ha-formfield>` </ha-formfield>`
: ""} : ""}
${this.backupHasPassword ${this.snapshotHasPassword
? html` ? html`
<paper-input <paper-input
.label=${this._localize("password")} .label=${this._localize("password")}
type="password" type="password"
name="backupPassword" name="snapshotPassword"
.value=${this.backupPassword} .value=${this.snapshotPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input> </paper-input>
${!this.backup ${!this.snapshot
? html` <paper-input ? html` <paper-input
.label=${this._localize("confirm_password")} .label=${this.supervisor?.localize("confirm_password")}
type="password" type="password"
name="confirmBackupPassword" name="confirmSnapshotPassword"
.value=${this.confirmBackupPassword} .value=${this.confirmSnapshotPassword}
@value-changed=${this._handleTextValueChanged} @value-changed=${this._handleTextValueChanged}
> >
</paper-input>` </paper-input>`
@@ -270,10 +274,6 @@ export class SupervisorBackupContent extends LitElement {
`; `;
} }
private toggleHomeAssistant() {
this.homeAssistant = !this.homeAssistant;
}
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
return css` return css`
.partial-picker ha-formfield { .partial-picker ha-formfield {
@@ -307,7 +307,7 @@ export class SupervisorBackupContent extends LitElement {
display: block; display: block;
margin: 0 -14px -16px; margin: 0 -14px -16px;
} }
.backup-types { .snapshot-types {
display: flex; display: flex;
margin-left: -13px; margin-left: -13px;
} }
@@ -317,23 +317,23 @@ export class SupervisorBackupContent extends LitElement {
`; `;
} }
public backupDetails(): public snapshotDetails():
| HassioPartialBackupCreateParams | HassioPartialSnapshotCreateParams
| HassioFullBackupCreateParams { | HassioFullSnapshotCreateParams {
const data: any = {}; const data: any = {};
if (!this.backup) { if (!this.snapshot) {
data.name = this.backupName || formatDate(new Date(), this.hass.locale); data.name = this.snapshotName || formatDate(new Date(), this.hass.locale);
} }
if (this.backupHasPassword) { if (this.snapshotHasPassword) {
data.password = this.backupPassword; data.password = this.snapshotPassword;
if (!this.backup) { if (!this.snapshot) {
data.confirm_password = this.confirmBackupPassword; data.confirm_password = this.confirmSnapshotPassword;
} }
} }
if (this.backupType === "full") { if (this.snapshotType === "full") {
return data; return data;
} }
@@ -415,7 +415,7 @@ export class SupervisorBackupContent extends LitElement {
} }
private _toggleHasPassword(): void { private _toggleHasPassword(): void {
this.backupHasPassword = !this.backupHasPassword; this.snapshotHasPassword = !this.snapshotHasPassword;
} }
private _toggleSection(ev): void { private _toggleSection(ev): void {
@@ -445,6 +445,6 @@ export class SupervisorBackupContent extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { 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 { customElement, property } from "lit/decorators";
import { atLeastVersion } from "../../../src/common/config/version"; import { atLeastVersion } from "../../../src/common/config/version";
import { navigate } from "../../../src/common/navigate"; import { navigate } from "../../../src/common/navigate";
import { caseInsensitiveStringCompare } from "../../../src/common/string/compare"; import { compare } from "../../../src/common/string/compare";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { Supervisor } from "../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../src/data/supervisor/supervisor";
import { haStyle } from "../../../src/resources/styles"; import { haStyle } from "../../../src/resources/styles";
@@ -33,7 +33,7 @@ class HassioAddons extends LitElement {
</ha-card> </ha-card>
` `
: this.supervisor.supervisor.addons : this.supervisor.supervisor.addons
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) .sort((a, b) => compare(a.name, b.name))
.map( .map(
(addon) => html` (addon) => html`
<ha-card .addon=${addon} @click=${this._addonTapped}> <ha-card .addon=${addon} @click=${this._addonTapped}>

View File

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

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,180 +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``;
}
return html`
<ha-dialog
open
scrimClickAction
escapeKeyAction
.heading=${this.moving
? this.dialogParams.supervisor.localize("dialog.datadisk_move.moving")
: this.dialogParams.supervisor.localize("dialog.datadisk_move.title")}
@closed=${this.closeDialog}
?hideActions=${this.moving}
>
${this.moving
? 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>`
: 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>`}
</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

@@ -4,7 +4,7 @@ import { customElement, property, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/common/search/search-input"; import "../../../../src/common/search/search-input";
import { stringCompare } from "../../../../src/common/string/compare"; import { compare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel"; import "../../../../src/components/ha-expansion-panel";
import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware"; import { HassioHardwareInfo } from "../../../../src/data/hassio/hardware";
@@ -27,7 +27,7 @@ const _filterDevices = memoizeOne(
.toLocaleLowerCase() .toLocaleLowerCase()
.includes(filter)) .includes(filter))
) )
.sort((a, b) => stringCompare(a.name, b.name)) .sort((a, b) => compare(a.name, b.name))
); );
@customElement("dialog-hassio-hardware") @customElement("dialog-hassio-hardware")

View File

@@ -61,6 +61,10 @@ class HassioMarkdownDialog extends LitElement {
app-toolbar [main-title] { app-toolbar [main-title] {
margin-left: 16px; margin-left: 16px;
} }
paper-checkbox {
display: block;
margin: 4px;
}
@media all and (max-width: 450px), all and (max-height: 500px) { @media all and (max-width: 450px), all and (max-height: 500px) {
ha-paper-dialog { ha-paper-dialog {
max-height: 100%; 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 { customElement, property, state } from "lit/decorators";
import { cache } from "lit/directives/cache"; import { cache } from "lit/directives/cache";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-expansion-panel"; import "../../../../src/components/ha-expansion-panel";
@@ -42,8 +41,7 @@ const IP_VERSIONS = ["ipv4", "ipv6"];
@customElement("dialog-hassio-network") @customElement("dialog-hassio-network")
export class DialogHassioNetwork export class DialogHassioNetwork
extends LitElement extends LitElement
implements HassDialog<HassioNetworkDialogParams> implements HassDialog<HassioNetworkDialogParams> {
{
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@property({ attribute: false }) public supervisor!: Supervisor; @property({ attribute: false }) public supervisor!: Supervisor;
@@ -252,9 +250,9 @@ export class DialogHassioNetwork
` `
: ""} : ""}
${this._dirty ${this._dirty
? html`<ha-alert alert-type="warning"> ? html`<div class="warning">
${this.supervisor.localize("dialog.network.warning")} ${this.supervisor.localize("dialog.network.warning")}
</ha-alert>` </div>`
: ""} : ""}
</div> </div>
<div class="buttons"> <div class="buttons">
@@ -287,7 +285,7 @@ export class DialogHassioNetwork
this.hass, this.hass,
this._interface.interface this._interface.interface
); );
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: "Failed to scan for accesspoints", title: "Failed to scan for accesspoints",
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -448,7 +446,7 @@ export class DialogHassioNetwork
this._interface!.interface, this._interface!.interface,
interfaceOptions interfaceOptions
); );
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("dialog.network.failed_to_change"), title: this.supervisor.localize("dialog.network.failed_to_change"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -494,7 +492,7 @@ export class DialogHassioNetwork
} }
private _handleRadioValueChangedAp(ev: CustomEvent): void { 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" | "open"
| "wep" | "wep"
| "wpa-psk"; | "wpa-psk";

View File

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

View File

@@ -9,8 +9,6 @@ import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import memoizeOne from "memoize-one"; import memoizeOne from "memoize-one";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { caseInsensitiveStringCompare } from "../../../../src/common/string/compare";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
@@ -58,7 +56,7 @@ class HassioRepositoriesDialog extends LitElement {
private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) => private _filteredRepositories = memoizeOne((repos: HassioAddonRepository[]) =>
repos repos
.filter((repo) => repo.slug !== "core" && repo.slug !== "local") .filter((repo) => repo.slug !== "core" && repo.slug !== "local")
.sort((a, b) => caseInsensitiveStringCompare(a.name, b.name)) .sort((a, b) => (a.name < b.name ? -1 : 1))
); );
protected render(): TemplateResult { protected render(): TemplateResult {
@@ -77,9 +75,7 @@ class HassioRepositoriesDialog extends LitElement {
this._dialogParams!.supervisor.localize("dialog.repositories.title") this._dialogParams!.supervisor.localize("dialog.repositories.title")
)} )}
> >
${this._error ${this._error ? html`<div class="error">${this._error}</div>` : ""}
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<div class="form"> <div class="form">
${repositories.length ${repositories.length
? repositories.map( ? repositories.map(
@@ -165,9 +161,9 @@ class HassioRepositoriesDialog extends LitElement {
public focus() { public focus() {
this.updateComplete.then(() => this.updateComplete.then(() =>
( (this.shadowRoot?.querySelector(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement "[dialogInitialFocus]"
)?.focus() ) as HTMLElement)?.focus()
); );
} }
@@ -186,7 +182,7 @@ class HassioRepositoriesDialog extends LitElement {
this._repositories = addonsinfo.repositories; this._repositories = addonsinfo.repositories;
fireEvent(this, "supervisor-collection-refresh", { collection: "addon" }); fireEvent(this, "supervisor-collection-refresh", { collection: "addon" });
} catch (err: any) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
} }
@@ -208,7 +204,7 @@ class HassioRepositoriesDialog extends LitElement {
await this._loadData(); await this._loadData();
input.value = ""; input.value = "";
} catch (err: any) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
this._processing = false; this._processing = false;
@@ -230,7 +226,7 @@ class HassioRepositoriesDialog extends LitElement {
addons_repositories: newRepositories, addons_repositories: newRepositories,
}); });
await this._loadData(); await this._loadData();
} catch (err: any) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
} }

View File

@@ -2,42 +2,41 @@ import "@material/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, query, state } from "lit/decorators"; import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import { createCloseHeading } from "../../../../src/components/ha-dialog"; import { createCloseHeading } from "../../../../src/components/ha-dialog";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
createHassioFullBackup, createHassioFullSnapshot,
createHassioPartialBackup, createHassioPartialSnapshot,
} from "../../../../src/data/hassio/backup"; } from "../../../../src/data/hassio/snapshot";
import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box"; import { showAlertDialog } from "../../../../src/dialogs/generic/show-dialog-box";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import "../../components/supervisor-backup-content"; import "../../components/supervisor-snapshot-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content"; import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioCreateBackupDialogParams } from "./show-dialog-hassio-create-backup"; import { HassioCreateSnapshotDialogParams } from "./show-dialog-hassio-create-snapshot";
@customElement("dialog-hassio-create-backup") @customElement("dialog-hassio-create-snapshot")
class HassioCreateBackupDialog extends LitElement { class HassioCreateSnapshotDialog extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _dialogParams?: HassioCreateBackupDialogParams; @state() private _dialogParams?: HassioCreateSnapshotDialogParams;
@state() private _error?: string; @state() private _error?: string;
@state() private _creatingBackup = false; @state() private _creatingSnapshot = false;
@query("supervisor-backup-content") @query("supervisor-snapshot-content")
private _backupContent!: SupervisorBackupContent; private _snapshotContent!: SupervisorSnapshotContent;
public showDialog(params: HassioCreateBackupDialogParams) { public showDialog(params: HassioCreateSnapshotDialogParams) {
this._dialogParams = params; this._dialogParams = params;
this._creatingBackup = false; this._creatingSnapshot = false;
} }
public closeDialog() { public closeDialog() {
this._dialogParams = undefined; this._dialogParams = undefined;
this._creatingBackup = false; this._creatingSnapshot = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
@@ -53,84 +52,82 @@ class HassioCreateBackupDialog extends LitElement {
@closed=${this.closeDialog} @closed=${this.closeDialog}
.heading=${createCloseHeading( .heading=${createCloseHeading(
this.hass, 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` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content : html`<supervisor-snapshot-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
> >
</supervisor-backup-content>`} </supervisor-snapshot-content>`}
${this._error ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<mwc-button slot="secondaryAction" @click=${this.closeDialog}> <mwc-button slot="secondaryAction" @click=${this.closeDialog}>
${this._dialogParams.supervisor.localize("common.close")} ${this._dialogParams.supervisor.localize("common.close")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button
.disabled=${this._creatingBackup} .disabled=${this._creatingSnapshot}
slot="primaryAction" slot="primaryAction"
@click=${this._createBackup} @click=${this._createSnapshot}
> >
${this._dialogParams.supervisor.localize("backup.create")} ${this._dialogParams.supervisor.localize("snapshot.create")}
</mwc-button> </mwc-button>
</ha-dialog> </ha-dialog>
`; `;
} }
private async _createBackup(): Promise<void> { private async _createSnapshot(): Promise<void> {
if (this._dialogParams!.supervisor.info.state !== "running") { if (this._dialogParams!.supervisor.info.state !== "running") {
showAlertDialog(this, { showAlertDialog(this, {
title: this._dialogParams!.supervisor.localize( title: this._dialogParams!.supervisor.localize(
"backup.could_not_create" "snapshot.could_not_create"
), ),
text: this._dialogParams!.supervisor.localize( text: this._dialogParams!.supervisor.localize(
"backup.create_blocked_not_running", "snapshot.create_blocked_not_running",
"state", "state",
this._dialogParams!.supervisor.info.state this._dialogParams!.supervisor.info.state
), ),
}); });
return; return;
} }
const backupDetails = this._backupContent.backupDetails(); const snapshotDetails = this._snapshotContent.snapshotDetails();
this._creatingBackup = true; this._creatingSnapshot = true;
this._error = ""; this._error = "";
if (backupDetails.password && !backupDetails.password.length) { if (snapshotDetails.password && !snapshotDetails.password.length) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"backup.enter_password" "snapshot.enter_password"
); );
this._creatingBackup = false; this._creatingSnapshot = false;
return; return;
} }
if ( if (
backupDetails.password && snapshotDetails.password &&
backupDetails.password !== backupDetails.confirm_password snapshotDetails.password !== snapshotDetails.confirm_password
) { ) {
this._error = this._dialogParams!.supervisor.localize( this._error = this._dialogParams!.supervisor.localize(
"backup.passwords_not_matching" "snapshot.passwords_not_matching"
); );
this._creatingBackup = false; this._creatingSnapshot = false;
return; return;
} }
delete backupDetails.confirm_password; delete snapshotDetails.confirm_password;
try { try {
if (this._backupContent.backupType === "full") { if (this._snapshotContent.snapshotType === "full") {
await createHassioFullBackup(this.hass, backupDetails); await createHassioFullSnapshot(this.hass, snapshotDetails);
} else { } else {
await createHassioPartialBackup(this.hass, backupDetails); await createHassioPartialSnapshot(this.hass, snapshotDetails);
} }
this._dialogParams!.onCreate(); this._dialogParams!.onCreate();
this.closeDialog(); this.closeDialog();
} catch (err: any) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
} }
this._creatingBackup = false; this._creatingSnapshot = false;
} }
static get styles(): CSSResultGroup { static get styles(): CSSResultGroup {
@@ -149,6 +146,6 @@ class HassioCreateBackupDialog extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { 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 { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyleDialog } from "../../../../src/resources/styles"; import { haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import "../../components/hassio-upload-backup"; import "../../components/hassio-upload-snapshot";
import { HassioBackupUploadDialogParams } from "./show-dialog-backup-upload"; import { HassioSnapshotUploadDialogParams } from "./show-dialog-snapshot-upload";
@customElement("dialog-hassio-backup-upload") @customElement("dialog-hassio-snapshot-upload")
export class DialogHassioBackupUpload export class DialogHassioSnapshotUpload
extends LitElement extends LitElement
implements HassDialog<HassioBackupUploadDialogParams> implements HassDialog<HassioSnapshotUploadDialogParams> {
{
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _params?: HassioBackupUploadDialogParams; @state() private _params?: HassioSnapshotUploadDialogParams;
public async showDialog( public async showDialog(
params: HassioBackupUploadDialogParams params: HassioSnapshotUploadDialogParams
): Promise<void> { ): Promise<void> {
this._params = params; this._params = params;
await this.updateComplete; await this.updateComplete;
@@ -27,8 +26,8 @@ export class DialogHassioBackupUpload
public closeDialog(): void { public closeDialog(): void {
if (this._params && !this._params.onboarding) { if (this._params && !this._params.onboarding) {
if (this._params.reloadBackup) { if (this._params.reloadSnapshot) {
this._params.reloadBackup(); this._params.reloadSnapshot();
} }
} }
this._params = undefined; this._params = undefined;
@@ -51,23 +50,23 @@ export class DialogHassioBackupUpload
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title"> Upload backup </span> <span slot="title"> Upload snapshot </span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
<hassio-upload-backup <hassio-upload-snapshot
@backup-uploaded=${this._backupUploaded} @snapshot-uploaded=${this._snapshotUploaded}
.hass=${this.hass} .hass=${this.hass}
></hassio-upload-backup> ></hassio-upload-snapshot>
</ha-dialog> </ha-dialog>
`; `;
} }
private _backupUploaded(ev) { private _snapshotUploaded(ev) {
const backup = ev.detail.backup; const snapshot = ev.detail.snapshot;
this._params?.showBackup(backup.slug); this._params?.showSnapshot(snapshot.slug);
this.closeDialog(); this.closeDialog();
} }
@@ -94,6 +93,6 @@ export class DialogHassioBackupUpload
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-backup-upload": DialogHassioBackupUpload; "dialog-hassio-snapshot-upload": DialogHassioSnapshotUpload;
} }
} }

View File

@@ -6,16 +6,15 @@ import { customElement, property, query, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import { slugify } from "../../../../src/common/string/slugify"; import { slugify } from "../../../../src/common/string/slugify";
import "../../../../src/components/buttons/ha-progress-button"; import "../../../../src/components/buttons/ha-progress-button";
import "../../../../src/components/ha-alert";
import "../../../../src/components/ha-button-menu"; import "../../../../src/components/ha-button-menu";
import "../../../../src/components/ha-header-bar"; import "../../../../src/components/ha-header-bar";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import { getSignedPath } from "../../../../src/data/auth"; import { getSignedPath } from "../../../../src/data/auth";
import { extractApiErrorMessage } from "../../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../../src/data/hassio/common";
import { import {
fetchHassioBackupInfo, fetchHassioSnapshotInfo,
HassioBackupDetail, HassioSnapshotDetail,
} from "../../../../src/data/hassio/backup"; } from "../../../../src/data/hassio/snapshot";
import { import {
showAlertDialog, showAlertDialog,
showConfirmationDialog, showConfirmationDialog,
@@ -24,46 +23,43 @@ import { HassDialog } from "../../../../src/dialogs/make-dialog-manager";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import { HomeAssistant } from "../../../../src/types"; import { HomeAssistant } from "../../../../src/types";
import { fileDownload } from "../../../../src/util/file_download"; import { fileDownload } from "../../../../src/util/file_download";
import "../../components/supervisor-backup-content"; import "../../components/supervisor-snapshot-content";
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content"; import type { SupervisorSnapshotContent } from "../../components/supervisor-snapshot-content";
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup"; import { HassioSnapshotDialogParams } from "./show-dialog-hassio-snapshot";
import { atLeastVersion } from "../../../../src/common/config/version";
import { stopPropagation } from "../../../../src/common/dom/stop_propagation";
@customElement("dialog-hassio-backup") @customElement("dialog-hassio-snapshot")
class HassioBackupDialog class HassioSnapshotDialog
extends LitElement extends LitElement
implements HassDialog<HassioBackupDialogParams> implements HassDialog<HassioSnapshotDialogParams> {
{
@property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public hass!: HomeAssistant;
@state() private _error?: string; @state() private _error?: string;
@state() private _backup?: HassioBackupDetail; @state() private _snapshot?: HassioSnapshotDetail;
@state() private _dialogParams?: HassioBackupDialogParams; @state() private _dialogParams?: HassioSnapshotDialogParams;
@state() private _restoringBackup = false; @state() private _restoringSnapshot = false;
@query("supervisor-backup-content") @query("supervisor-snapshot-content")
private _backupContent!: SupervisorBackupContent; private _snapshotContent!: SupervisorSnapshotContent;
public async showDialog(params: HassioBackupDialogParams) { public async showDialog(params: HassioSnapshotDialogParams) {
this._backup = await fetchHassioBackupInfo(this.hass, params.slug); this._snapshot = await fetchHassioSnapshotInfo(this.hass, params.slug);
this._dialogParams = params; this._dialogParams = params;
this._restoringBackup = false; this._restoringSnapshot = false;
} }
public closeDialog() { public closeDialog() {
this._backup = undefined; this._snapshot = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
this._restoringBackup = false; this._restoringSnapshot = false;
this._error = undefined; this._error = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._dialogParams || !this._backup) { if (!this._dialogParams || !this._snapshot) {
return html``; return html``;
} }
return html` return html`
@@ -75,28 +71,26 @@ class HassioBackupDialog
> >
<div slot="heading"> <div slot="heading">
<ha-header-bar> <ha-header-bar>
<span slot="title">${this._backup.name}</span> <span slot="title">${this._snapshot.name}</span>
<mwc-icon-button slot="actionItems" dialogAction="cancel"> <mwc-icon-button slot="actionItems" dialogAction="cancel">
<ha-svg-icon .path=${mdiClose}></ha-svg-icon> <ha-svg-icon .path=${mdiClose}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
</ha-header-bar> </ha-header-bar>
</div> </div>
${this._restoringBackup ${this._restoringSnapshot
? html` <ha-circular-progress active></ha-circular-progress>` ? html` <ha-circular-progress active></ha-circular-progress>`
: html`<supervisor-backup-content : html`<supervisor-snapshot-content
.hass=${this.hass} .hass=${this.hass}
.supervisor=${this._dialogParams.supervisor} .supervisor=${this._dialogParams.supervisor}
.backup=${this._backup} .snapshot=${this._snapshot}
.onboarding=${this._dialogParams.onboarding || false} .onboarding=${this._dialogParams.onboarding || false}
.localize=${this._dialogParams.localize} .localize=${this._dialogParams.localize}
> >
</supervisor-backup-content>`} </supervisor-snapshot-content>`}
${this._error ${this._error ? html`<p class="error">Error: ${this._error}</p>` : ""}
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
<mwc-button <mwc-button
.disabled=${this._restoringBackup} .disabled=${this._restoringSnapshot}
slot="secondaryAction" slot="secondaryAction"
@click=${this._restoreClicked} @click=${this._restoreClicked}
> >
@@ -108,13 +102,13 @@ class HassioBackupDialog
fixed fixed
slot="primaryAction" slot="primaryAction"
@action=${this._handleMenuAction} @action=${this._handleMenuAction}
@closed=${stopPropagation} @closed=${(ev: Event) => ev.stopPropagation()}
> >
<mwc-icon-button slot="trigger" alt="menu"> <mwc-icon-button slot="trigger" alt="menu">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item>Download Backup</mwc-list-item> <mwc-list-item>Download Snapshot</mwc-list-item>
<mwc-list-item class="error">Delete Backup</mwc-list-item> <mwc-list-item class="error">Delete Snapshot</mwc-list-item>
</ha-button-menu>` </ha-button-menu>`
: ""} : ""}
</ha-dialog> </ha-dialog>
@@ -155,30 +149,30 @@ class HassioBackupDialog
} }
private async _restoreClicked() { private async _restoreClicked() {
const backupDetails = this._backupContent.backupDetails(); const snapshotDetails = this._snapshotContent.snapshotDetails();
this._restoringBackup = true; this._restoringSnapshot = true;
if (this._backupContent.backupType === "full") { if (this._snapshotContent.snapshotType === "full") {
await this._fullRestoreClicked(backupDetails); await this._fullRestoreClicked(snapshotDetails);
} else { } else {
await this._partialRestoreClicked(backupDetails); await this._partialRestoreClicked(snapshotDetails);
} }
this._restoringBackup = false; this._restoringSnapshot = false;
} }
private async _partialRestoreClicked(backupDetails) { private async _partialRestoreClicked(snapshotDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore backup", title: "Could not restore snapshot",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want partially to restore this backup?", title: "Are you sure you want partially to restore this snapshot?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -191,12 +185,8 @@ class HassioBackupDialog
.callApi( .callApi(
"POST", "POST",
`hassio/${ `hassio/snapshots/${this._snapshot!.slug}/restore/partial`,
atLeastVersion(this.hass.config.version, 2021, 9) snapshotDetails
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/partial`,
backupDetails
) )
.then( .then(
() => { () => {
@@ -208,29 +198,29 @@ class HassioBackupDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/partial`, { fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/partial`, {
method: "POST", method: "POST",
body: JSON.stringify(backupDetails), body: JSON.stringify(snapshotDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
} }
private async _fullRestoreClicked(backupDetails) { private async _fullRestoreClicked(snapshotDetails) {
if ( if (
this._dialogParams?.supervisor !== undefined && this._dialogParams?.supervisor !== undefined &&
this._dialogParams?.supervisor.info.state !== "running" this._dialogParams?.supervisor.info.state !== "running"
) { ) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: "Could not restore backup", title: "Could not restore snapshot",
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`, text: `Restoring a snapshot is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
}); });
return; return;
} }
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: title:
"Are you sure you want to wipe your system and restore this backup?", "Are you sure you want to wipe your system and restore this snapshot?",
confirmText: "restore", confirmText: "restore",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -242,12 +232,8 @@ class HassioBackupDialog
this.hass this.hass
.callApi( .callApi(
"POST", "POST",
`hassio/${ `hassio/snapshots/${this._snapshot!.slug}/restore/full`,
atLeastVersion(this.hass.config.version, 2021, 9) snapshotDetails
? "backups"
: "snapshots"
}/${this._backup!.slug}/restore/full`,
backupDetails
) )
.then( .then(
() => { () => {
@@ -259,9 +245,9 @@ class HassioBackupDialog
); );
} else { } else {
fireEvent(this, "restoring"); fireEvent(this, "restoring");
fetch(`/api/hassio/backups/${this._backup!.slug}/restore/full`, { fetch(`/api/hassio/snapshots/${this._snapshot!.slug}/restore/full`, {
method: "POST", method: "POST",
body: JSON.stringify(backupDetails), body: JSON.stringify(snapshotDetails),
}); });
this.closeDialog(); this.closeDialog();
} }
@@ -270,7 +256,7 @@ class HassioBackupDialog
private async _deleteClicked() { private async _deleteClicked() {
if ( if (
!(await showConfirmationDialog(this, { !(await showConfirmationDialog(this, {
title: "Are you sure you want to delete this backup?", title: "Are you sure you want to delete this snapshot?",
confirmText: "delete", confirmText: "delete",
dismissText: "cancel", dismissText: "cancel",
})) }))
@@ -280,14 +266,7 @@ class HassioBackupDialog
this.hass this.hass
.callApi( .callApi("POST", `hassio/snapshots/${this._snapshot!.slug}/remove`)
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( .then(
() => { () => {
if (this._dialogParams!.onDelete) { if (this._dialogParams!.onDelete) {
@@ -306,13 +285,9 @@ class HassioBackupDialog
try { try {
signedPath = await getSignedPath( signedPath = await getSignedPath(
this.hass, this.hass,
`/api/hassio/${ `/api/hassio/snapshots/${this._snapshot!.slug}/download`
atLeastVersion(this.hass.config.version, 2021, 9)
? "backups"
: "snapshots"
}/${this._backup!.slug}/download`
); );
} catch (err: any) { } catch (err) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
}); });
@@ -322,7 +297,8 @@ class HassioBackupDialog
if (window.location.href.includes("ui.nabu.casa")) { if (window.location.href.includes("ui.nabu.casa")) {
const confirm = await showConfirmationDialog(this, { const confirm = await showConfirmationDialog(this, {
title: "Potential slow download", 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?", 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", confirmText: "continue",
dismissText: "cancel", dismissText: "cancel",
}); });
@@ -334,19 +310,19 @@ class HassioBackupDialog
fileDownload( fileDownload(
this, this,
signedPath.path, signedPath.path,
`home_assistant_backup_${slugify(this._computeName)}.tar` `home_assistant_snapshot_${slugify(this._computeName)}.tar`
); );
} }
private get _computeName() { private get _computeName() {
return this._backup return this._snapshot
? this._backup.name || this._backup.slug ? this._snapshot.name || this._snapshot.slug
: "Unnamed backup"; : "Unnamed snapshot";
} }
} }
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
"dialog-hassio-backup": HassioBackupDialog; "dialog-hassio-snapshot": HassioSnapshotDialog;
} }
} }

View File

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

View File

@@ -2,7 +2,7 @@ import { fireEvent } from "../../../../src/common/dom/fire_event";
import { LocalizeFunc } from "../../../../src/common/translations/localize"; import { LocalizeFunc } from "../../../../src/common/translations/localize";
import { Supervisor } from "../../../../src/data/supervisor/supervisor"; import { Supervisor } from "../../../../src/data/supervisor/supervisor";
export interface HassioBackupDialogParams { export interface HassioSnapshotDialogParams {
slug: string; slug: string;
onDelete?: () => void; onDelete?: () => void;
onboarding?: boolean; onboarding?: boolean;
@@ -10,13 +10,13 @@ export interface HassioBackupDialogParams {
localize?: LocalizeFunc; localize?: LocalizeFunc;
} }
export const showHassioBackupDialog = ( export const showHassioSnapshotDialog = (
element: HTMLElement, element: HTMLElement,
dialogParams: HassioBackupDialogParams dialogParams: HassioSnapshotDialogParams
): void => { ): void => {
fireEvent(element, "show-dialog", { fireEvent(element, "show-dialog", {
dialogTag: "dialog-hassio-backup", dialogTag: "dialog-hassio-snapshot",
dialogImport: () => import("./dialog-hassio-backup"), dialogImport: () => import("./dialog-hassio-snapshot"),
dialogParams, 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) { if (confirmed) {
try { try {
await restartHassioAddon(hass, addon.slug); await restartHassioAddon(hass, addon.slug);
} catch (err: any) { } catch (err) {
showAlertDialog(element, { showAlertDialog(element, {
title: supervisor.localize( title: supervisor.localize(
"common.failed_to_restart_name", "common.failed_to_restart_name",

View File

@@ -2,20 +2,32 @@ import "@material/mwc-button/mwc-button";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators"; import { customElement, state } from "lit/decorators";
import { fireEvent } from "../../../../src/common/dom/fire_event"; import { fireEvent } from "../../../../src/common/dom/fire_event";
import "../../../../src/components/ha-alert"; import "../../../../src/components/ha-checkbox";
import "../../../../src/components/ha-circular-progress"; import "../../../../src/components/ha-circular-progress";
import "../../../../src/components/ha-dialog"; import "../../../../src/components/ha-dialog";
import "../../../../src/components/ha-settings-row"; import "../../../../src/components/ha-settings-row";
import "../../../../src/components/ha-svg-icon"; import "../../../../src/components/ha-svg-icon";
import "../../../../src/components/ha-switch";
import { import {
extractApiErrorMessage, extractApiErrorMessage,
ignoreSupervisorError, ignoreSupervisorError,
} from "../../../../src/data/hassio/common"; } from "../../../../src/data/hassio/common";
import { createHassioPartialBackup } from "../../../../src/data/hassio/backup"; import {
SupervisorFrontendPrefrences,
fetchSupervisorFrontendPreferences,
saveSupervisorFrontendPreferences,
} from "../../../../src/data/supervisor/supervisor";
import { createHassioPartialSnapshot } from "../../../../src/data/hassio/snapshot";
import { haStyle, haStyleDialog } from "../../../../src/resources/styles"; import { haStyle, haStyleDialog } from "../../../../src/resources/styles";
import type { HomeAssistant } from "../../../../src/types"; import type { HomeAssistant } from "../../../../src/types";
import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update"; import { SupervisorDialogSupervisorUpdateParams } from "./show-dialog-update";
import memoizeOne from "memoize-one";
const snapshot_before_update = memoizeOne(
(slug: string, frontendPrefrences: SupervisorFrontendPrefrences) =>
slug in frontendPrefrences.snapshot_before_update
? frontendPrefrences.snapshot_before_update[slug]
: true
);
@customElement("dialog-supervisor-update") @customElement("dialog-supervisor-update")
class DialogSupervisorUpdate extends LitElement { class DialogSupervisorUpdate extends LitElement {
@@ -23,12 +35,12 @@ class DialogSupervisorUpdate extends LitElement {
@state() private _opened = false; @state() private _opened = false;
@state() private _createBackup = true; @state() private _action: "snapshot" | "update" | null = null;
@state() private _action: "backup" | "update" | null = null;
@state() private _error?: string; @state() private _error?: string;
@state() private _frontendPrefrences?: SupervisorFrontendPrefrences;
@state() @state()
private _dialogParams?: SupervisorDialogSupervisorUpdateParams; private _dialogParams?: SupervisorDialogSupervisorUpdateParams;
@@ -37,27 +49,30 @@ class DialogSupervisorUpdate extends LitElement {
): Promise<void> { ): Promise<void> {
this._opened = true; this._opened = true;
this._dialogParams = params; this._dialogParams = params;
this._frontendPrefrences = await fetchSupervisorFrontendPreferences(
this.hass
);
await this.updateComplete; await this.updateComplete;
} }
public closeDialog(): void { public closeDialog(): void {
this._action = null; this._action = null;
this._createBackup = true;
this._error = undefined; this._error = undefined;
this._dialogParams = undefined; this._dialogParams = undefined;
this._frontendPrefrences = undefined;
fireEvent(this, "dialog-closed", { dialog: this.localName }); fireEvent(this, "dialog-closed", { dialog: this.localName });
} }
public focus(): void { public focus(): void {
this.updateComplete.then(() => this.updateComplete.then(() =>
( (this.shadowRoot?.querySelector(
this.shadowRoot?.querySelector("[dialogInitialFocus]") as HTMLElement "[dialogInitialFocus]"
)?.focus() ) as HTMLElement)?.focus()
); );
} }
protected render(): TemplateResult { protected render(): TemplateResult {
if (!this._dialogParams) { if (!this._dialogParams || !this._frontendPrefrences) {
return html``; return html``;
} }
return html` return html`
@@ -83,24 +98,28 @@ class DialogSupervisorUpdate extends LitElement {
</div> </div>
<ha-settings-row> <ha-settings-row>
<ha-checkbox
.checked=${snapshot_before_update(
this._dialogParams.slug,
this._frontendPrefrences
)}
haptic
@click=${this._toggleSnapshot}
slot="prefix"
>
</ha-checkbox>
<span slot="heading"> <span slot="heading">
${this._dialogParams.supervisor.localize( ${this._dialogParams.supervisor.localize(
"dialog.update.backup" "dialog.update.snapshot"
)} )}
</span> </span>
<span slot="description"> <span slot="description">
${this._dialogParams.supervisor.localize( ${this._dialogParams.supervisor.localize(
"dialog.update.create_backup", "dialog.update.create_snapshot",
"name", "name",
this._dialogParams.name this._dialogParams.name
)} )}
</span> </span>
<ha-switch
.checked=${this._createBackup}
haptic
@click=${this._toggleBackup}
>
</ha-switch>
</ha-settings-row> </ha-settings-row>
<mwc-button @click=${this.closeDialog} slot="secondaryAction"> <mwc-button @click=${this.closeDialog} slot="secondaryAction">
${this._dialogParams.supervisor.localize("common.cancel")} ${this._dialogParams.supervisor.localize("common.cancel")}
@@ -124,31 +143,44 @@ class DialogSupervisorUpdate extends LitElement {
this._dialogParams.version this._dialogParams.version
) )
: this._dialogParams.supervisor.localize( : this._dialogParams.supervisor.localize(
"dialog.update.creating_backup", "dialog.update.snapshotting",
"name", "name",
this._dialogParams.name this._dialogParams.name
)} )}
</p>`} </p>`}
${this._error ${this._error ? html`<p class="error">${this._error}</p>` : ""}
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
</ha-dialog> </ha-dialog>
`; `;
} }
private _toggleBackup() { private async _toggleSnapshot(): Promise<void> {
this._createBackup = !this._createBackup; this._frontendPrefrences!.snapshot_before_update[
this._dialogParams!.slug
] = !snapshot_before_update(
this._dialogParams!.slug,
this._frontendPrefrences!
);
await saveSupervisorFrontendPreferences(
this.hass,
this._frontendPrefrences!
);
} }
private async _update() { private async _update() {
if (this._createBackup) { if (
this._action = "backup"; snapshot_before_update(
this._dialogParams!.slug,
this._frontendPrefrences!
)
) {
this._action = "snapshot";
try { try {
await createHassioPartialBackup( await createHassioPartialSnapshot(
this.hass, this.hass,
this._dialogParams!.backupParams this._dialogParams!.snapshotParams
); );
} catch (err: any) { } catch (err) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
this._action = null; this._action = null;
return; return;
@@ -158,11 +190,11 @@ class DialogSupervisorUpdate extends LitElement {
this._action = "update"; this._action = "update";
try { try {
await this._dialogParams!.updateHandler!(); await this._dialogParams!.updateHandler!();
} catch (err: any) { } catch (err) {
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
this._error = extractApiErrorMessage(err); this._error = extractApiErrorMessage(err);
this._action = null;
} }
this._action = null;
return; return;
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -91,7 +91,7 @@ class HassioIngressView extends LitElement {
if (requestedAddon) { if (requestedAddon) {
try { try {
addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon); addonInfo = await fetchHassioAddonInfo(this.hass, requestedAddon);
} catch (err: any) { } catch (err) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
title: requestedAddon, title: requestedAddon,
@@ -145,7 +145,7 @@ class HassioIngressView extends LitElement {
try { try {
addon = await fetchHassioAddonInfo(this.hass, addonSlug); addon = await fetchHassioAddonInfo(this.hass, addonSlug);
} catch (err: any) { } catch (err) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: "Unable to fetch add-on info to start Ingress", text: "Unable to fetch add-on info to start Ingress",
title: "Supervisor", title: "Supervisor",
@@ -179,7 +179,7 @@ class HassioIngressView extends LitElement {
try { try {
session = await createSessionPromise; session = await createSessionPromise;
} catch (err: any) { } catch (err) {
await showAlertDialog(this, { await showAlertDialog(this, {
text: "Unable to create an Ingress session", text: "Unable to create an Ingress session",
title: addon.name, title: addon.name,
@@ -195,7 +195,7 @@ class HassioIngressView extends LitElement {
this._sessionKeepAlive = window.setInterval(async () => { this._sessionKeepAlive = window.setInterval(async () => {
try { try {
await validateHassioSession(this.hass, session); await validateHassioSession(this.hass, session);
} catch (err: any) { } catch (err) {
session = await createHassioSession(this.hass); session = await createHassioSession(this.hass);
} }
}, 60000); }, 60000);

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import "@material/mwc-button"; import "@material/mwc-button";
import { ActionDetail } from "@material/mwc-list/mwc-list-foundation";
import "@material/mwc-list/mwc-list-item"; import "@material/mwc-list/mwc-list-item";
import { mdiDotsVertical } from "@mdi/js"; import { mdiDotsVertical } from "@mdi/js";
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
@@ -39,9 +40,8 @@ import {
roundWithOneDecimal, roundWithOneDecimal,
} from "../../../src/util/calculate"; } from "../../../src/util/calculate";
import "../components/supervisor-metric"; import "../components/supervisor-metric";
import { showHassioDatadiskDialog } from "../dialogs/datadisk/show-dialog-hassio-datadisk";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { showNetworkDialog } from "../dialogs/network/show-dialog-network"; import { showNetworkDialog } from "../dialogs/network/show-dialog-network";
import { showHassioHardwareDialog } from "../dialogs/hardware/show-dialog-hassio-hardware";
import { hassioStyle } from "../resources/hassio-style"; import { hassioStyle } from "../resources/hassio-style";
@customElement("hassio-host-info") @customElement("hassio-host-info")
@@ -113,7 +113,7 @@ class HassioHostInfo extends LitElement {
` `
: ""} : ""}
</ha-settings-row> </ha-settings-row>
${!this.supervisor.host.features.includes("haos") ${!this.supervisor.host.features.includes("hassos")
? html`<ha-settings-row> ? html`<ha-settings-row>
<span slot="heading"> <span slot="heading">
${this.supervisor.localize("system.host.docker_version")} ${this.supervisor.localize("system.host.docker_version")}
@@ -180,38 +180,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"> <mwc-icon-button slot="trigger">
<ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon> <ha-svg-icon .path=${mdiDotsVertical}></ha-svg-icon>
</mwc-icon-button> </mwc-icon-button>
<mwc-list-item <mwc-list-item>
.action=${"hardware"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize("system.host.hardware")} ${this.supervisor.localize("system.host.hardware")}
</mwc-list-item> </mwc-list-item>
${this.supervisor.host.features.includes("haos") ${this.supervisor.host.features.includes("hassos")
? html` ? html`<mwc-list-item>
<mwc-list-item
.action=${"import_from_usb"}
@click=${this._handleMenuAction}
>
${this.supervisor.localize("system.host.import_from_usb")} ${this.supervisor.localize("system.host.import_from_usb")}
</mwc-list-item> </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>
`
: ""}
`
: ""} : ""}
</ha-button-menu> </ha-button-menu>
</div> </div>
@@ -234,31 +216,22 @@ class HassioHostInfo extends LitElement {
return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0]; return network_info.interfaces.find((a) => a.primary)?.ipv4?.address![0];
}); });
private async _handleMenuAction(ev) { private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
switch ((ev.target as any).action) { switch (ev.detail.index) {
case "hardware": case 0:
await this._showHardware(); await this._showHardware();
break; break;
case "import_from_usb": case 1:
await this._importFromUSB(); await this._importFromUSB();
break; break;
case "move_datadisk":
await this._moveDatadisk();
break;
} }
} }
private _moveDatadisk(): void {
showHassioDatadiskDialog(this, {
supervisor: this.supervisor,
});
}
private async _showHardware(): Promise<void> { private async _showHardware(): Promise<void> {
let hardware; let hardware;
try { try {
hardware = await fetchHassioHardwareInfo(this.hass); hardware = await fetchHassioHardwareInfo(this.hass);
} catch (err: any) { } catch (err) {
await showAlertDialog(this, { await showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.host.failed_to_get_hardware_list" "system.host.failed_to_get_hardware_list"
@@ -288,7 +261,7 @@ class HassioHostInfo extends LitElement {
try { try {
await rebootHost(this.hass); await rebootHost(this.hass);
} catch (err: any) { } catch (err) {
// Ignore connection errors, these are all expected // Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, { showAlertDialog(this, {
@@ -318,7 +291,7 @@ class HassioHostInfo extends LitElement {
try { try {
await shutdownHost(this.hass); await shutdownHost(this.hass);
} catch (err: any) { } catch (err) {
// Ignore connection errors, these are all expected // Ignore connection errors, these are all expected
if (this.hass.connection.connected && !ignoreSupervisorError(err)) { if (this.hass.connection.connected && !ignoreSupervisorError(err)) {
showAlertDialog(this, { showAlertDialog(this, {
@@ -359,7 +332,7 @@ class HassioHostInfo extends LitElement {
try { try {
await updateOS(this.hass); await updateOS(this.hass);
fireEvent(this, "supervisor-collection-refresh", { collection: "os" }); fireEvent(this, "supervisor-collection-refresh", { collection: "os" });
} catch (err: any) { } catch (err) {
if (this.hass.connection.connected) { if (this.hass.connection.connected) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
@@ -397,7 +370,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "host", collection: "host",
}); });
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("system.host.failed_to_set_hostname"), title: this.supervisor.localize("system.host.failed_to_set_hostname"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -412,7 +385,7 @@ class HassioHostInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "host", collection: "host",
}); });
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.host.failed_to_import_from_usb" "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 { atLeastVersion } from "../../../src/common/config/version";
import { fireEvent } from "../../../src/common/dom/fire_event"; import { fireEvent } from "../../../src/common/dom/fire_event";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-settings-row";
import "../../../src/components/ha-switch"; import "../../../src/components/ha-switch";
@@ -34,18 +33,16 @@ import { hassioStyle } from "../resources/hassio-style";
const UNSUPPORTED_REASON_URL = { const UNSUPPORTED_REASON_URL = {
apparmor: "/more-info/unsupported/apparmor", apparmor: "/more-info/unsupported/apparmor",
container: "/more-info/unsupported/container", container: "/more-info/unsupported/container",
content_trust: "/more-info/unsupported/content_trust",
dbus: "/more-info/unsupported/dbus", dbus: "/more-info/unsupported/dbus",
docker_configuration: "/more-info/unsupported/docker_configuration", docker_configuration: "/more-info/unsupported/docker_configuration",
docker_version: "/more-info/unsupported/docker_version", docker_version: "/more-info/unsupported/docker_version",
job_conditions: "/more-info/unsupported/job_conditions", job_conditions: "/more-info/unsupported/job_conditions",
lxc: "/more-info/unsupported/lxc", lxc: "/more-info/unsupported/lxc",
network_manager: "/more-info/unsupported/network_manager", network_manager: "/more-info/unsupported/network_manager",
os_agent: "/more-info/unsupported/os_agent",
os: "/more-info/unsupported/os", os: "/more-info/unsupported/os",
privileged: "/more-info/unsupported/privileged", privileged: "/more-info/unsupported/privileged",
source_mods: "/more-info/unsupported/source_mods",
systemd: "/more-info/unsupported/systemd", systemd: "/more-info/unsupported/systemd",
content_trust: "/more-info/unsupported/content_trust",
}; };
const UNHEALTHY_REASON_URL = { const UNHEALTHY_REASON_URL = {
@@ -173,25 +170,31 @@ class HassioSupervisorInfo extends LitElement {
></ha-switch> ></ha-switch>
</ha-settings-row>` </ha-settings-row>`
: "" : ""
: html`<ha-alert : html`<div class="error">
alert-type="warning"
.actionText=${this.supervisor.localize("common.learn_more")}
@alert-action-clicked=${this._unsupportedDialog}
>
${this.supervisor.localize( ${this.supervisor.localize(
"system.supervisor.unsupported_title" "system.supervisor.unsupported_title"
)} )}
</ha-alert>`} <button
${!this.supervisor.supervisor.healthy class="link"
? html`<ha-alert .title=${this.supervisor.localize("common.learn_more")}
alert-type="error" @click=${this._unsupportedDialog}
.actionText=${this.supervisor.localize("common.learn_more")}
@alert-action-clicked=${this._unhealthyDialog}
> >
Learn more
</button>
</div>`}
${!this.supervisor.supervisor.healthy
? html`<div class="error">
${this.supervisor.localize( ${this.supervisor.localize(
"system.supervisor.unhealthy_title" "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>
<div class="metrics-block"> <div class="metrics-block">
@@ -282,7 +285,7 @@ class HassioSupervisorInfo extends LitElement {
}; };
await setSupervisorOption(this.hass, data); await setSupervisorOption(this.hass, data);
await this._reloadSupervisor(); await this._reloadSupervisor();
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.supervisor.failed_to_set_option" "system.supervisor.failed_to_set_option"
@@ -300,7 +303,7 @@ class HassioSupervisorInfo extends LitElement {
try { try {
await this._reloadSupervisor(); await this._reloadSupervisor();
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize("system.supervisor.failed_to_reload"), title: this.supervisor.localize("system.supervisor.failed_to_reload"),
text: extractApiErrorMessage(err), text: extractApiErrorMessage(err),
@@ -343,7 +346,7 @@ class HassioSupervisorInfo extends LitElement {
try { try {
await restartSupervisor(this.hass); await restartSupervisor(this.hass);
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"common.failed_to_restart_name", "common.failed_to_restart_name",
@@ -388,7 +391,7 @@ class HassioSupervisorInfo extends LitElement {
fireEvent(this, "supervisor-collection-refresh", { fireEvent(this, "supervisor-collection-refresh", {
collection: "supervisor", collection: "supervisor",
}); });
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"common.failed_to_update_name", "common.failed_to_update_name",
@@ -427,10 +430,10 @@ class HassioSupervisorInfo extends LitElement {
<li> <li>
${UNSUPPORTED_REASON_URL[reason] ${UNSUPPORTED_REASON_URL[reason]
? html`<a ? html`<a
href=${documentationUrl( href="${documentationUrl(
this.hass, this.hass,
UNSUPPORTED_REASON_URL[reason] UNSUPPORTED_REASON_URL[reason]
)} )}"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
@@ -458,10 +461,10 @@ class HassioSupervisorInfo extends LitElement {
<li> <li>
${UNHEALTHY_REASON_URL[reason] ${UNHEALTHY_REASON_URL[reason]
? html`<a ? html`<a
href=${documentationUrl( href="${documentationUrl(
this.hass, this.hass,
UNHEALTHY_REASON_URL[reason] UNHEALTHY_REASON_URL[reason]
)} )}"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
@@ -483,7 +486,7 @@ class HassioSupervisorInfo extends LitElement {
diagnostics: !this.supervisor.supervisor?.diagnostics, diagnostics: !this.supervisor.supervisor?.diagnostics,
}; };
await setSupervisorOption(this.hass, data); await setSupervisorOption(this.hass, data);
} catch (err: any) { } catch (err) {
showAlertDialog(this, { showAlertDialog(this, {
title: this.supervisor.localize( title: this.supervisor.localize(
"system.supervisor.failed_to_set_option" "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 { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators"; import { customElement, property, state } from "lit/decorators";
import "../../../src/components/buttons/ha-progress-button"; import "../../../src/components/buttons/ha-progress-button";
import "../../../src/components/ha-alert";
import "../../../src/components/ha-card"; import "../../../src/components/ha-card";
import { extractApiErrorMessage } from "../../../src/data/hassio/common"; import { extractApiErrorMessage } from "../../../src/data/hassio/common";
import { fetchHassioLogs } from "../../../src/data/hassio/supervisor"; import { fetchHassioLogs } from "../../../src/data/hassio/supervisor";
@@ -68,9 +67,7 @@ class HassioSupervisorLog extends LitElement {
protected render(): TemplateResult | void { protected render(): TemplateResult | void {
return html` return html`
<ha-card> <ha-card>
${this._error ${this._error ? html` <div class="errors">${this._error}</div> ` : ""}
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
: ""}
${this.hass.userData?.showAdvanced ${this.hass.userData?.showAdvanced
? html` ? html`
<paper-dropdown-menu <paper-dropdown-menu
@@ -130,7 +127,7 @@ class HassioSupervisorLog extends LitElement {
this.hass, this.hass,
this._selectedLogProvider this._selectedLogProvider
); );
} catch (err: any) { } catch (err) {
this._error = this.supervisor.localize( this._error = this.supervisor.localize(
"system.log.get_logs", "system.log.get_logs",
"provider", "provider",
@@ -157,6 +154,10 @@ class HassioSupervisorLog extends LitElement {
padding: 0 2%; padding: 0 2%;
width: 96%; width: 96%;
} }
.errors {
color: var(--error-color);
margin-bottom: 16px;
}
`, `,
]; ];
} }

View File

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

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