mirror of
https://github.com/home-assistant/core.git
synced 2025-07-13 08:17:08 +00:00
Update frontend
This commit is contained in:
parent
cd87c40bbf
commit
e10b00f341
@ -1,30 +1,13 @@
|
|||||||
"""Handle the frontend for Home Assistant."""
|
"""Handle the frontend for Home Assistant."""
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
import logging
|
|
||||||
|
|
||||||
from . import version, mdi_version
|
from . import version, mdi_version
|
||||||
from homeassistant.const import URL_ROOT
|
|
||||||
from homeassistant.components import api
|
from homeassistant.components import api
|
||||||
from homeassistant.components.http import HomeAssistantView
|
from homeassistant.components.http import HomeAssistantView
|
||||||
|
|
||||||
DOMAIN = 'frontend'
|
DOMAIN = 'frontend'
|
||||||
DEPENDENCIES = ['api']
|
DEPENDENCIES = ['api']
|
||||||
|
|
||||||
INDEX_PATH = os.path.join(os.path.dirname(__file__), 'index.html.template')
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
FRONTEND_URLS = [
|
|
||||||
URL_ROOT, '/logbook', '/history', '/map', '/devService', '/devState',
|
|
||||||
'/devEvent', '/devInfo', '/devTemplate',
|
|
||||||
re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)'),
|
|
||||||
]
|
|
||||||
|
|
||||||
URL_API_BOOTSTRAP = "/api/bootstrap"
|
|
||||||
|
|
||||||
_FINGERPRINT = re.compile(r'^(\w+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
|
|
||||||
|
|
||||||
|
|
||||||
def setup(hass, config):
|
def setup(hass, config):
|
||||||
"""Setup serving the frontend."""
|
"""Setup serving the frontend."""
|
||||||
@ -39,7 +22,8 @@ def setup(hass, config):
|
|||||||
|
|
||||||
hass.wsgi.register_static_path(
|
hass.wsgi.register_static_path(
|
||||||
"/service_worker.js",
|
"/service_worker.js",
|
||||||
os.path.join(www_static_path, sw_path)
|
os.path.join(www_static_path, sw_path),
|
||||||
|
0
|
||||||
)
|
)
|
||||||
hass.wsgi.register_static_path("/static", www_static_path)
|
hass.wsgi.register_static_path("/static", www_static_path)
|
||||||
hass.wsgi.register_static_path("/local", hass.config.path('www'))
|
hass.wsgi.register_static_path("/local", hass.config.path('www'))
|
||||||
@ -50,7 +34,7 @@ def setup(hass, config):
|
|||||||
class BootstrapView(HomeAssistantView):
|
class BootstrapView(HomeAssistantView):
|
||||||
"""View to bootstrap frontend with all needed data."""
|
"""View to bootstrap frontend with all needed data."""
|
||||||
|
|
||||||
url = URL_API_BOOTSTRAP
|
url = "/api/bootstrap"
|
||||||
name = "api:bootstrap"
|
name = "api:bootstrap"
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
@ -66,7 +50,7 @@ class BootstrapView(HomeAssistantView):
|
|||||||
class IndexView(HomeAssistantView):
|
class IndexView(HomeAssistantView):
|
||||||
"""Serve the frontend."""
|
"""Serve the frontend."""
|
||||||
|
|
||||||
url = URL_ROOT
|
url = '/'
|
||||||
name = "frontend:index"
|
name = "frontend:index"
|
||||||
requires_auth = False
|
requires_auth = False
|
||||||
extra_urls = ['/logbook', '/history', '/map', '/devService', '/devState',
|
extra_urls = ['/logbook', '/history', '/map', '/devService', '/devState',
|
||||||
@ -92,11 +76,11 @@ class IndexView(HomeAssistantView):
|
|||||||
else:
|
else:
|
||||||
app_url = "frontend-{}.html".format(version.VERSION)
|
app_url = "frontend-{}.html".format(version.VERSION)
|
||||||
|
|
||||||
# auto login if no password was set, else check api_password param
|
# auto login if no password was set
|
||||||
if self.hass.config.api.api_password is None:
|
if self.hass.config.api.api_password is None:
|
||||||
auth = 'no_password_set'
|
auth = 'no_password_set'
|
||||||
else:
|
else:
|
||||||
auth = request.values.get('api_password', '')
|
auth = ''
|
||||||
|
|
||||||
template = self.templates.get_template('index.html')
|
template = self.templates.get_template('index.html')
|
||||||
|
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Home Assistant</title>
|
|
||||||
|
|
||||||
<link rel='manifest' href='/static/manifest.json'>
|
|
||||||
<link rel='icon' href='/static/favicon.ico'>
|
|
||||||
<link rel='apple-touch-icon' sizes='180x180'
|
|
||||||
href='/static/favicon-apple-180x180.png'>
|
|
||||||
<meta name='apple-mobile-web-app-capable' content='yes'>
|
|
||||||
<meta name='mobile-web-app-capable' content='yes'>
|
|
||||||
<meta name='viewport' content='width=device-width, user-scalable=no'>
|
|
||||||
<meta name='theme-color' content='#03a9f4'>
|
|
||||||
<style>
|
|
||||||
#ha-init-skeleton {
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: flex;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
-webkit-justify-content: center;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
margin-bottom: 97px;
|
|
||||||
font-family: Roboto, sans-serif;
|
|
||||||
font-size: 0pt;
|
|
||||||
transition: font-size 2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ha-init-skeleton paper-spinner {
|
|
||||||
height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ha-init-skeleton a {
|
|
||||||
color: #03A9F4;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ha-init-skeleton.error {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ha-init-skeleton.error img,
|
|
||||||
#ha-init-skeleton.error paper-spinner {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
function initError() {
|
|
||||||
document
|
|
||||||
.getElementById('ha-init-skeleton')
|
|
||||||
.classList.add('error');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<link rel='import' href='/static/{{ app_url }}' onerror='initError()' async>
|
|
||||||
</head>
|
|
||||||
<body fullbleed>
|
|
||||||
<div id='ha-init-skeleton'>
|
|
||||||
<img src='/static/favicon-192x192.png' height='192'>
|
|
||||||
<paper-spinner active></paper-spinner>
|
|
||||||
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
var webComponentsSupported = (
|
|
||||||
'registerElement' in document &&
|
|
||||||
'import' in document.createElement('link') &&
|
|
||||||
'content' in document.createElement('template'));
|
|
||||||
if (!webComponentsSupported) {
|
|
||||||
var script = document.createElement('script')
|
|
||||||
script.async = true
|
|
||||||
script.onerror = initError;
|
|
||||||
script.src = '/static/webcomponents-lite.min.js'
|
|
||||||
document.head.appendChild(script)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<home-assistant auth='{{ auth }}' icons='{{ icons }}'></home-assistant>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -28,20 +28,55 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
margin-bottom: 123px;
|
margin-bottom: 97px;
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
font-size: 0pt;
|
||||||
|
transition: font-size 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ha-init-skeleton paper-spinner {
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ha-init-skeleton a {
|
||||||
|
color: #03A9F4;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ha-init-skeleton.error {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ha-init-skeleton.error img,
|
||||||
|
#ha-init-skeleton.error paper-spinner {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel='import' href='/static/{{ app_url }}' async>
|
<script>
|
||||||
|
function initError() {
|
||||||
|
document
|
||||||
|
.getElementById('ha-init-skeleton')
|
||||||
|
.classList.add('error');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel='import' href='/static/{{ app_url }}' onerror='initError()' async>
|
||||||
</head>
|
</head>
|
||||||
<body fullbleed>
|
<body fullbleed>
|
||||||
<div id='ha-init-skeleton'><img src='/static/favicon-192x192.png' height='192'></div>
|
<div id='ha-init-skeleton'>
|
||||||
|
<img src='/static/favicon-192x192.png' height='192'>
|
||||||
|
<paper-spinner active></paper-spinner>
|
||||||
|
Home Assistant had trouble<br>connecting to the server.<br><br><a href='/'>TRY AGAIN</a>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
var webComponentsSupported = ('registerElement' in document &&
|
var webComponentsSupported = (
|
||||||
'import' in document.createElement('link') &&
|
'registerElement' in document &&
|
||||||
'content' in document.createElement('template'))
|
'import' in document.createElement('link') &&
|
||||||
|
'content' in document.createElement('template'));
|
||||||
if (!webComponentsSupported) {
|
if (!webComponentsSupported) {
|
||||||
var script = document.createElement('script')
|
var script = document.createElement('script')
|
||||||
script.async = true
|
script.async = true
|
||||||
|
script.onerror = initError;
|
||||||
script.src = '/static/webcomponents-lite.min.js'
|
script.src = '/static/webcomponents-lite.min.js'
|
||||||
document.head.appendChild(script)
|
document.head.appendChild(script)
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
"""DO NOT MODIFY. Auto-generated by build_frontend script."""
|
||||||
VERSION = "45a2660086388e0ac7d61e3442c4d847"
|
VERSION = "61a4974868291c31d0b189d962750e76"
|
||||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1 +1 @@
|
|||||||
Subproject commit 6d2dd25a4cab6ff105b42497a3c1684ee7ab138b
|
Subproject commit 0be98873d7044f387645f3a694e41660be663b66
|
@ -1 +1,258 @@
|
|||||||
!function(e){function t(r){if(n[r])return n[r].exports;var s=n[r]={i:r,l:!1,exports:{}};return e[r].call(s.exports,s,s.exports,t),s.l=!0,s.exports}var n={};return t.m=e,t.c=n,t.p="",t(t.s=194)}({194:function(e,t,n){var r="0.10",s="/",c=["/","/logbook","/history","/map","/devService","/devState","/devEvent","/devInfo","/states"],i=["/static/favicon-192x192.png"];self.addEventListener("install",function(e){e.waitUntil(caches.open(r).then(function(e){return e.addAll(i.concat(s))}))}),self.addEventListener("activate",function(e){}),self.addEventListener("message",function(e){}),self.addEventListener("fetch",function(e){var t=e.request.url.substr(e.request.url.indexOf("/",8));i.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(e.request)})),c.includes(t)&&e.respondWith(caches.open(r).then(function(t){return t.match(s).then(function(n){return n||fetch(e.request).then(function(e){return t.put(s,e.clone()),e})})}))})}});
|
/**
|
||||||
|
* Copyright 2016 Google Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This generated service worker JavaScript will precache your site's resources.
|
||||||
|
// The code needs to be saved in a .js file at the top-level of your site, and registered
|
||||||
|
// from your pages in order to be used. See
|
||||||
|
// https://github.com/googlechrome/sw-precache/blob/master/demo/app/js/service-worker-registration.js
|
||||||
|
// for an example of how you can register this script and handle various service worker events.
|
||||||
|
|
||||||
|
/* eslint-env worker, serviceworker */
|
||||||
|
/* eslint-disable indent, no-unused-vars, no-multiple-empty-lines, max-nested-callbacks, space-before-function-paren */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* eslint-disable quotes, comma-spacing */
|
||||||
|
var PrecacheConfig = [["/","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/devEvent","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/devInfo","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/devService","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/devState","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/devTemplate","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/history","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/logbook","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/map","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/states","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/static/frontend-61a4974868291c31d0b189d962750e76.html","2e74fd3303cf5bbeb72a2f1b4d18fccd"],["/static/mdi-9ee3d4466a65bef35c2c8974e91b37c0.html","9a6846935116cd29279c91e0ee0a26d0"],["static/favicon-192x192.png","419903b8422586a7e28021bbe9011175"],["static/fonts/roboto/Roboto-Bold.ttf","d329cc8b34667f114a95422aaad1b063"],["static/fonts/roboto/Roboto-Light.ttf","7b5fb88f12bec8143f00e21bc3222124"],["static/fonts/roboto/Roboto-Medium.ttf","fe13e4170719c2fc586501e777bde143"],["static/fonts/roboto/Roboto-Regular.ttf","ac3f799d5bbaf5196fab15ab8de8431c"],["static/images/card_media_player_bg.png","a34281d1c1835d338a642e90930e61aa"],["static/webcomponents-lite.min.js","b0f32ad3c7749c40d486603f31c9d8b1"]];
|
||||||
|
/* eslint-enable quotes, comma-spacing */
|
||||||
|
var CacheNamePrefix = 'sw-precache-v1--' + (self.registration ? self.registration.scope : '') + '-';
|
||||||
|
|
||||||
|
|
||||||
|
var IgnoreUrlParametersMatching = [/^utm_/];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var addDirectoryIndex = function (originalUrl, index) {
|
||||||
|
var url = new URL(originalUrl);
|
||||||
|
if (url.pathname.slice(-1) === '/') {
|
||||||
|
url.pathname += index;
|
||||||
|
}
|
||||||
|
return url.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
var getCacheBustedUrl = function (url, param) {
|
||||||
|
param = param || Date.now();
|
||||||
|
|
||||||
|
var urlWithCacheBusting = new URL(url);
|
||||||
|
urlWithCacheBusting.search += (urlWithCacheBusting.search ? '&' : '') +
|
||||||
|
'sw-precache=' + param;
|
||||||
|
|
||||||
|
return urlWithCacheBusting.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
var isPathWhitelisted = function (whitelist, absoluteUrlString) {
|
||||||
|
// If the whitelist is empty, then consider all URLs to be whitelisted.
|
||||||
|
if (whitelist.length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise compare each path regex to the path of the URL passed in.
|
||||||
|
var path = (new URL(absoluteUrlString)).pathname;
|
||||||
|
return whitelist.some(function(whitelistedPathRegex) {
|
||||||
|
return path.match(whitelistedPathRegex);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var populateCurrentCacheNames = function (precacheConfig,
|
||||||
|
cacheNamePrefix, baseUrl) {
|
||||||
|
var absoluteUrlToCacheName = {};
|
||||||
|
var currentCacheNamesToAbsoluteUrl = {};
|
||||||
|
|
||||||
|
precacheConfig.forEach(function(cacheOption) {
|
||||||
|
var absoluteUrl = new URL(cacheOption[0], baseUrl).toString();
|
||||||
|
var cacheName = cacheNamePrefix + absoluteUrl + '-' + cacheOption[1];
|
||||||
|
currentCacheNamesToAbsoluteUrl[cacheName] = absoluteUrl;
|
||||||
|
absoluteUrlToCacheName[absoluteUrl] = cacheName;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
absoluteUrlToCacheName: absoluteUrlToCacheName,
|
||||||
|
currentCacheNamesToAbsoluteUrl: currentCacheNamesToAbsoluteUrl
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var stripIgnoredUrlParameters = function (originalUrl,
|
||||||
|
ignoreUrlParametersMatching) {
|
||||||
|
var url = new URL(originalUrl);
|
||||||
|
|
||||||
|
url.search = url.search.slice(1) // Exclude initial '?'
|
||||||
|
.split('&') // Split into an array of 'key=value' strings
|
||||||
|
.map(function(kv) {
|
||||||
|
return kv.split('='); // Split each 'key=value' string into a [key, value] array
|
||||||
|
})
|
||||||
|
.filter(function(kv) {
|
||||||
|
return ignoreUrlParametersMatching.every(function(ignoredRegex) {
|
||||||
|
return !ignoredRegex.test(kv[0]); // Return true iff the key doesn't match any of the regexes.
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.map(function(kv) {
|
||||||
|
return kv.join('='); // Join each [key, value] array into a 'key=value' string
|
||||||
|
})
|
||||||
|
.join('&'); // Join the array of 'key=value' strings into a string with '&' in between each
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var mappings = populateCurrentCacheNames(PrecacheConfig, CacheNamePrefix, self.location);
|
||||||
|
var AbsoluteUrlToCacheName = mappings.absoluteUrlToCacheName;
|
||||||
|
var CurrentCacheNamesToAbsoluteUrl = mappings.currentCacheNamesToAbsoluteUrl;
|
||||||
|
|
||||||
|
function deleteAllCaches() {
|
||||||
|
return caches.keys().then(function(cacheNames) {
|
||||||
|
return Promise.all(
|
||||||
|
cacheNames.map(function(cacheName) {
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('install', function(event) {
|
||||||
|
event.waitUntil(
|
||||||
|
// Take a look at each of the cache names we expect for this version.
|
||||||
|
Promise.all(Object.keys(CurrentCacheNamesToAbsoluteUrl).map(function(cacheName) {
|
||||||
|
return caches.open(cacheName).then(function(cache) {
|
||||||
|
// Get a list of all the entries in the specific named cache.
|
||||||
|
// For caches that are already populated for a given version of a
|
||||||
|
// resource, there should be 1 entry.
|
||||||
|
return cache.keys().then(function(keys) {
|
||||||
|
// If there are 0 entries, either because this is a brand new version
|
||||||
|
// of a resource or because the install step was interrupted the
|
||||||
|
// last time it ran, then we need to populate the cache.
|
||||||
|
if (keys.length === 0) {
|
||||||
|
// Use the last bit of the cache name, which contains the hash,
|
||||||
|
// as the cache-busting parameter.
|
||||||
|
// See https://github.com/GoogleChrome/sw-precache/issues/100
|
||||||
|
var cacheBustParam = cacheName.split('-').pop();
|
||||||
|
var urlWithCacheBusting = getCacheBustedUrl(
|
||||||
|
CurrentCacheNamesToAbsoluteUrl[cacheName], cacheBustParam);
|
||||||
|
|
||||||
|
var request = new Request(urlWithCacheBusting,
|
||||||
|
{credentials: 'same-origin'});
|
||||||
|
return fetch(request).then(function(response) {
|
||||||
|
if (response.ok) {
|
||||||
|
return cache.put(CurrentCacheNamesToAbsoluteUrl[cacheName],
|
||||||
|
response);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('Request for %s returned a response status %d, ' +
|
||||||
|
'so not attempting to cache it.',
|
||||||
|
urlWithCacheBusting, response.status);
|
||||||
|
// Get rid of the empty cache if we can't add a successful response to it.
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})).then(function() {
|
||||||
|
return caches.keys().then(function(allCacheNames) {
|
||||||
|
return Promise.all(allCacheNames.filter(function(cacheName) {
|
||||||
|
return cacheName.indexOf(CacheNamePrefix) === 0 &&
|
||||||
|
!(cacheName in CurrentCacheNamesToAbsoluteUrl);
|
||||||
|
}).map(function(cacheName) {
|
||||||
|
return caches.delete(cacheName);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}).then(function() {
|
||||||
|
if (typeof self.skipWaiting === 'function') {
|
||||||
|
// Force the SW to transition from installing -> active state
|
||||||
|
self.skipWaiting();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (self.clients && (typeof self.clients.claim === 'function')) {
|
||||||
|
self.addEventListener('activate', function(event) {
|
||||||
|
event.waitUntil(self.clients.claim());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener('message', function(event) {
|
||||||
|
if (event.data.command === 'delete_all') {
|
||||||
|
console.log('About to delete all caches...');
|
||||||
|
deleteAllCaches().then(function() {
|
||||||
|
console.log('Caches deleted.');
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
error: null
|
||||||
|
});
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.log('Caches not deleted:', error);
|
||||||
|
event.ports[0].postMessage({
|
||||||
|
error: error
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
self.addEventListener('fetch', function(event) {
|
||||||
|
if (event.request.method === 'GET') {
|
||||||
|
var urlWithoutIgnoredParameters = stripIgnoredUrlParameters(event.request.url,
|
||||||
|
IgnoreUrlParametersMatching);
|
||||||
|
|
||||||
|
var cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
|
||||||
|
var directoryIndex = 'index.html';
|
||||||
|
if (!cacheName && directoryIndex) {
|
||||||
|
urlWithoutIgnoredParameters = addDirectoryIndex(urlWithoutIgnoredParameters, directoryIndex);
|
||||||
|
cacheName = AbsoluteUrlToCacheName[urlWithoutIgnoredParameters];
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigateFallback = '';
|
||||||
|
// Ideally, this would check for event.request.mode === 'navigate', but that is not widely
|
||||||
|
// supported yet:
|
||||||
|
// https://code.google.com/p/chromium/issues/detail?id=540967
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1209081
|
||||||
|
if (!cacheName && navigateFallback && event.request.headers.has('accept') &&
|
||||||
|
event.request.headers.get('accept').includes('text/html') &&
|
||||||
|
/* eslint-disable quotes, comma-spacing */
|
||||||
|
isPathWhitelisted([], event.request.url)) {
|
||||||
|
/* eslint-enable quotes, comma-spacing */
|
||||||
|
var navigateFallbackUrl = new URL(navigateFallback, self.location);
|
||||||
|
cacheName = AbsoluteUrlToCacheName[navigateFallbackUrl.toString()];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cacheName) {
|
||||||
|
event.respondWith(
|
||||||
|
// Rely on the fact that each cache we manage should only have one entry, and return that.
|
||||||
|
caches.open(cacheName).then(function(cache) {
|
||||||
|
return cache.keys().then(function(keys) {
|
||||||
|
return cache.match(keys[0]).then(function(response) {
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
// If for some reason the response was deleted from the cache,
|
||||||
|
// raise and exception and fall back to the fetch() triggered in the catch().
|
||||||
|
throw Error('The cache ' + cacheName + ' is empty.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(function(e) {
|
||||||
|
console.warn('Couldn\'t serve response for "%s" from cache: %O', event.request.url, e);
|
||||||
|
return fetch(event.request);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Binary file not shown.
@ -232,15 +232,18 @@ class HomeAssistantWSGI(object):
|
|||||||
|
|
||||||
self.url_map.add(Rule(url, redirect_to=redirect_to))
|
self.url_map.add(Rule(url, redirect_to=redirect_to))
|
||||||
|
|
||||||
def register_static_path(self, url_root, path):
|
def register_static_path(self, url_root, path, cache_length=31):
|
||||||
"""Register a folder to serve as a static path."""
|
"""Register a folder to serve as a static path.
|
||||||
|
|
||||||
|
Specify optional cache length of asset in days.
|
||||||
|
"""
|
||||||
from static import Cling
|
from static import Cling
|
||||||
|
|
||||||
headers = []
|
headers = []
|
||||||
|
|
||||||
if not self.development:
|
if cache_length and not self.development:
|
||||||
# 1 year in seconds
|
# 1 year in seconds
|
||||||
cache_time = 365 * 86400
|
cache_time = cache_length * 86400
|
||||||
|
|
||||||
headers.append({
|
headers.append({
|
||||||
'prefix': '',
|
'prefix': '',
|
||||||
|
@ -7,9 +7,12 @@ npm run frontend_prod
|
|||||||
|
|
||||||
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..
|
||||||
cp build/frontend.html ..
|
cp build/frontend.html ..
|
||||||
cp build/service_worker.js ..
|
|
||||||
gzip build/frontend.html -c -k -9 > ../frontend.html.gz
|
gzip build/frontend.html -c -k -9 > ../frontend.html.gz
|
||||||
|
|
||||||
|
node script/sw-precache.js
|
||||||
|
cp build/service_worker.js ..
|
||||||
|
gzip build/service_worker.js -c -k -9 > ../service_worker.js.gz
|
||||||
|
|
||||||
# Generate the MD5 hash of the new frontend
|
# Generate the MD5 hash of the new frontend
|
||||||
cd ../..
|
cd ../..
|
||||||
echo '"""DO NOT MODIFY. Auto-generated by build_frontend script."""' > version.py
|
echo '"""DO NOT MODIFY. Auto-generated by build_frontend script."""' > version.py
|
||||||
|
@ -77,17 +77,6 @@ class TestFrontend(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(200, req.status_code)
|
self.assertEqual(200, req.status_code)
|
||||||
|
|
||||||
def test_auto_filling_in_api_password(self):
|
|
||||||
"""Test for auto filling of API password."""
|
|
||||||
req = requests.get(
|
|
||||||
_url("?{}={}".format(http.DATA_API_PASSWORD, API_PASSWORD)))
|
|
||||||
|
|
||||||
self.assertEqual(200, req.status_code)
|
|
||||||
|
|
||||||
auth_text = re.search(r"auth='{}'".format(API_PASSWORD), req.text)
|
|
||||||
|
|
||||||
self.assertIsNotNone(auth_text)
|
|
||||||
|
|
||||||
def test_404(self):
|
def test_404(self):
|
||||||
"""Test for HTTP 404 error."""
|
"""Test for HTTP 404 error."""
|
||||||
self.assertEqual(404, requests.get(_url("/not-existing")).status_code)
|
self.assertEqual(404, requests.get(_url("/not-existing")).status_code)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user