From 0f542fc97b2497021d22bd310906eec7c0dcc28a Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 21 Oct 2024 12:56:25 +0200 Subject: [PATCH 01/33] Add landing-page project --- .vscode/tasks.json | 32 +++++ build-scripts/bundle.cjs | 16 +++ build-scripts/gulp/clean.js | 11 ++ build-scripts/gulp/entry-html.js | 21 +++ build-scripts/gulp/gather-static.js | 16 +++ build-scripts/gulp/landing-page.js | 44 ++++++ build-scripts/gulp/webpack.js | 21 +++ build-scripts/paths.cjs | 12 ++ build-scripts/webpack.cjs | 4 + landing-page/.eslintrc.json | 6 + .../public/static/icons/favicon-192x192.png | Bin 0 -> 2017 bytes landing-page/public/static/icons/favicon.ico | Bin 0 -> 15086 bytes .../public/static/images/logo_discord.png | Bin 0 -> 1298 bytes landing-page/public/static/images/logo_x.svg | 3 + landing-page/rollup.config.js | 10 ++ landing-page/script/build_landing_page | 9 ++ landing-page/script/develop | 9 ++ landing-page/src/entrypoint.js | 3 + landing-page/src/ha-landing-page.ts | 125 ++++++++++++++++++ landing-page/src/html/index.html.template | 53 ++++++++ landing-page/webpack.config.js | 8 ++ 21 files changed, 403 insertions(+) create mode 100644 build-scripts/gulp/landing-page.js create mode 100644 landing-page/.eslintrc.json create mode 100644 landing-page/public/static/icons/favicon-192x192.png create mode 100644 landing-page/public/static/icons/favicon.ico create mode 100644 landing-page/public/static/images/logo_discord.png create mode 100644 landing-page/public/static/images/logo_x.svg create mode 100644 landing-page/rollup.config.js create mode 100755 landing-page/script/build_landing_page create mode 100755 landing-page/script/develop create mode 100644 landing-page/src/entrypoint.js create mode 100644 landing-page/src/ha-landing-page.ts create mode 100644 landing-page/src/html/index.html.template create mode 100644 landing-page/webpack.config.js diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 91f8252e8fcd..676e27e6d9b9 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -100,6 +100,38 @@ "instanceLimit": 1 } }, + { + "label": "Develop Landing Page", + "type": "gulp", + "task": "develop-landing-page", + "problemMatcher": { + "owner": "ha-build", + "source": "ha-build", + "fileLocation": "absolute", + "severity": "error", + "pattern": [ + { + "regexp": "(SyntaxError): (.+): (.+) \\((\\d+):(\\d+)\\)", + "severity": 1, + "file": 2, + "message": 3, + "line": 4, + "column": 5 + } + ], + "background": { + "activeOnStart": true, + "beginsPattern": "Changes detected. Starting compilation", + "endsPattern": "Build done @" + } + }, + + "isBackground": true, + "group": "build", + "runOptions": { + "instanceLimit": 1 + } + }, { "label": "Develop Demo", "type": "gulp", diff --git a/build-scripts/bundle.cjs b/build-scripts/bundle.cjs index 6b2f308b3d3a..5e394ae3ceba 100644 --- a/build-scripts/bundle.cjs +++ b/build-scripts/bundle.cjs @@ -327,4 +327,20 @@ module.exports.config = { }, }; }, + + landingPage({ isProdBuild, latestBuild }) { + return { + name: "landing-page" + nameSuffix(latestBuild), + entry: { + entrypoint: path.resolve(paths.landingPage_dir, "src/entrypoint.js"), + }, + outputPath: outputPath(paths.landingPage_output_root, latestBuild), + publicPath: publicPath(latestBuild), + isProdBuild, + latestBuild, + defineOverlay: { + __DEMO__: true, + }, + }; + }, }; diff --git a/build-scripts/gulp/clean.js b/build-scripts/gulp/clean.js index 9a21f6ccd8c3..b7c570c9d492 100644 --- a/build-scripts/gulp/clean.js +++ b/build-scripts/gulp/clean.js @@ -38,3 +38,14 @@ gulp.task( ]) ) ); + +gulp.task( + "clean-landing-page", + gulp.parallel("clean-translations", async () => + deleteSync([ + paths.landingPage_output_root, + paths.landingPage_build, + paths.build_dir, + ]) + ) +); diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.js index d8bf587f6c01..64f151e48f9b 100644 --- a/build-scripts/gulp/entry-html.js +++ b/build-scripts/gulp/entry-html.js @@ -257,6 +257,27 @@ gulp.task( ) ); +const LANDING_PAGE_PAGE_ENTRIES = { "index.html": ["entrypoint"] }; + +gulp.task( + "gen-pages-landing-page-dev", + genPagesDevTask( + LANDING_PAGE_PAGE_ENTRIES, + paths.landingPage_dir, + paths.landingPage_output_root + ) +); + +gulp.task( + "gen-pages-landing-page-prod", + genPagesProdTask( + LANDING_PAGE_PAGE_ENTRIES, + paths.landingPage_dir, + paths.landingPage_output_root, + paths.landingPage_output_latest + ) +); + const HASSIO_PAGE_ENTRIES = { "entrypoint.js": ["entrypoint"] }; gulp.task( diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index 653e863dcde7..12495235377e 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -199,3 +199,19 @@ gulp.task("copy-static-gallery", async () => { copyLocaleData(paths.gallery_output_static); copyMdiIcons(paths.gallery_output_static); }); + +gulp.task("copy-static-landing-page", async () => { + // Copy app static files + fs.copySync(polyPath("public/static"), paths.gallery_output_static); + // Copy gallery static files + fs.copySync( + path.resolve(paths.landingPage_dir, "public"), + paths.landingPage_output_root + ); + + copyMapPanel(paths.landingPage_output_static); + copyFonts(paths.landingPage_output_static); + copyTranslations(paths.landingPage_output_static); + copyLocaleData(paths.landingPage_output_static); + copyMdiIcons(paths.landingPage_output_static); +}); diff --git a/build-scripts/gulp/landing-page.js b/build-scripts/gulp/landing-page.js new file mode 100644 index 000000000000..b54cade42ad6 --- /dev/null +++ b/build-scripts/gulp/landing-page.js @@ -0,0 +1,44 @@ +import gulp from "gulp"; +import env from "../env.cjs"; +import "./clean.js"; +import "./entry-html.js"; +import "./gather-static.js"; +import "./gen-icons-json.js"; +import "./rollup.js"; +import "./service-worker.js"; +import "./translations.js"; +import "./webpack.js"; + +gulp.task( + "develop-landing-page", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "development"; + }, + "clean-landing-page", + "translations-enable-merge-backend", + gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), + "copy-static-landing-page", + "gen-pages-landing-page-dev", + gulp.parallel( + env.useRollup() + ? "rollup-dev-server-landing-page" + : "webpack-dev-server-landing-page" + ) + ) +); + +gulp.task( + "build-landing-page", + gulp.series( + async function setEnv() { + process.env.NODE_ENV = "production"; + }, + "clean-landing-page", + "translations-enable-merge-backend", + gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), + "copy-static-landing-page", + env.useRollup() ? "rollup-prod-landing-page" : "webpack-prod-landing-page", + "gen-pages-landing-page-prod" + ) +); diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index a7eb3be20d8e..a87bb4aa51d2 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -14,6 +14,7 @@ import { createDemoConfig, createGalleryConfig, createHassioConfig, + createLandingPageConfig, } from "../webpack.cjs"; const bothBuilds = (createConfigFunc, params) => [ @@ -199,3 +200,23 @@ gulp.task("webpack-prod-gallery", () => }) ) ); + +gulp.task("webpack-dev-server-landing-page", () => + runDevServer({ + compiler: webpack( + createLandingPageConfig({ isProdBuild: false, latestBuild: true }) + ), + contentBase: paths.landingPage_output_root, + port: 8110, + listenHost: "0.0.0.0", + }) +); + +gulp.task("webpack-prod-landing-page", () => + prodBuild( + createLandingPageConfig({ + isProdBuild: true, + latestBuild: true, + }) + ) +); diff --git a/build-scripts/paths.cjs b/build-scripts/paths.cjs index dff1d4bca491..adc790a25eb4 100644 --- a/build-scripts/paths.cjs +++ b/build-scripts/paths.cjs @@ -33,6 +33,18 @@ module.exports = { ), gallery_output_static: path.resolve(__dirname, "../gallery/dist/static"), + landingPage_dir: path.resolve(__dirname, "../landing-page"), + landingPage_build: path.resolve(__dirname, "../landing-page/build"), + landingPage_output_root: path.resolve(__dirname, "../landing-page/dist"), + landingPage_output_latest: path.resolve( + __dirname, + "../landing-page/dist/frontend_latest" + ), + landingPage_output_static: path.resolve( + __dirname, + "../landing-page/dist/static" + ), + hassio_dir: path.resolve(__dirname, "../hassio"), hassio_output_root: path.resolve(__dirname, "../hassio/build"), hassio_output_static: path.resolve(__dirname, "../hassio/build/static"), diff --git a/build-scripts/webpack.cjs b/build-scripts/webpack.cjs index 83f54fb58453..1d5ac1bb9482 100644 --- a/build-scripts/webpack.cjs +++ b/build-scripts/webpack.cjs @@ -283,11 +283,15 @@ const createHassioConfig = ({ const createGalleryConfig = ({ isProdBuild, latestBuild }) => createWebpackConfig(bundle.config.gallery({ isProdBuild, latestBuild })); +const createLandingPageConfig = ({ isProdBuild, latestBuild }) => + createWebpackConfig(bundle.config.landingPage({ isProdBuild, latestBuild })); + module.exports = { createAppConfig, createDemoConfig, createCastConfig, createHassioConfig, createGalleryConfig, + createLandingPageConfig, createWebpackConfig, }; diff --git a/landing-page/.eslintrc.json b/landing-page/.eslintrc.json new file mode 100644 index 000000000000..59c30e3b6d13 --- /dev/null +++ b/landing-page/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "../.eslintrc.json", + "rules": { + "no-console": 0 + } +} diff --git a/landing-page/public/static/icons/favicon-192x192.png b/landing-page/public/static/icons/favicon-192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..5f644d2083c34710b60f3a91a6feee2b00f3bb17 GIT binary patch literal 2017 zcmV<72Oju|P)Px#32;bRa{vGqB>(^xB>_oNB=7(L08~&+R7Ff_aT~q! z^7Q!_yz&^l@}b}N7`*Zryz&^k@ff`E7`*Zryz&^l@)*4F7`*cG^!XLL^7Zxk?DP3e z%JeqG^C7?UxaIhY+V*hM_0sJ4I ztnFSG000J=Nkl*;vOQ#8Ei*|mPOGRxnS>;CF1{=HqR#0KGf zDeE7tyquHHpO|1He^P?Y{0Rv(`I8Z7^Cu!OPwu*x5yz%qYy0_*&d2^{iAC2-0gk-#y3Gy>=RkqA8UMSmIREYf^87ms81nBbV9CFufGPiO z0=E1+2^jP5B4EwGgMc|dyTCHPc(iB)R{4dHL%%Au_Jg!BFbZt)i+WJw+;vUUb@Ql( zT7(x^_5U!Rx+%>BEOJR_apuXp_`-#D6s1PqS*m7xSy*B{mC0G$eSHMPn=%&iX4&S9=acK6>&oBuwELfn(>?!Sfsup9y^ zfnEO#)fk%+dyj$T`c00e8Pv3+toIezu0M@4$aW492yEB?qF>BYXz8s0z5?s#7qNC` zK)2cf`~>#TUqZDF;jh?J;PCti0$OW;yTIl77o0-&Bj7A>dVU9g#9eO>a1*%IAE+;* z?gH2P3GI!{UEo~*Azezi3*75hl#4lcfmi*_w482>U21k-`Ng-YkmPi{h6Sw;x3@5pZgcK+yzASf2rnIfK6%9T|l>gF}zHpV%TE`gmC{w^quz_ z&RRgZf5ZpS3rP3B7y&u~?fxAnU|T@E|Hum15>W4-@&bMf-Yeq$UuHmERzKen&#!e_ zfclz$&;NF+Qv=kew{H_DtJ4FN*_m$w?E&~UiO$D5ML>Og4nb9>2`DSR0oVob9D=+| z6;S3(m!RDM**nWUU^l?$y`ZXN0SHl-QkJ#F@u-Sx2H69IkiD9UaV#d)qmOvq<`B;e zvIh)mT$=WyHOu{o7j_QiWds0mFJrVX=c5GfI&`VS1H@3g7LwYoK-Ez%hb%lm^jr1d zcEzwhg*V=z+~Q6o?;&X?~U=m0Qi6ubR`W9@ID|F8UUP3fX^lb2lyV4`Wu1& z`)|Pem}cnv0DQtL^7{aEBLmOp1O~{d^E@XT769dseKiW@Z#otA^8lD1bH>pI@OZ5g z^>!x$0O!X%)#Cx89(pz1n(7g6M#2C%|AIN`js*z07V~HAuUY0|h3aM?0FfVc!VU{q z$L_zkBiptcS3dw^{;V|_K*(u%E&VO#6J!7Y$ltb0fjps!0f=GT`d^G?Co^C(|FbxK zpQJ^PeF6D^%D;WPHwlb@t^DoSt74pQZ7kGKqW3ru0I4gTnF?nnz-Is7dPCH5xHW0B zMS}rME^ihZ0Nefl7)@cjmv(?J=!?E~sb8(@0lNNoyyYL9Cy;rys|6VQp8)_oGA+oG z;TM3o{}ChLSo+fdsQVuVxR(C50p$G;0>rQY3zk9P0rdS316<1|eW1Q6wi0l*y61pxq5{f~K|QBG3<0FdQJJkSj0<(x)f1km+AW(16P<0^AfEw%zJfVTh9wb6d;S}TDTK%5^75K`T% zyX|M;vV{d20CoRc><XpznX%J+iOQ zhk}Al07HJ~O+HAl0pRF=k6lVYumNDn@3GBl7W@M6^uO0WvAInC3Si3bwb5-7yaKrL z`|Z>l1iz{hO!+;J8t4W4yv5`V&S2{2ml{Fi5-AA z{1!O?K0X;c020Ix00fMHau)#`pd@^k(j;I5oG9OBH3--M8R=Is^a3tGS<$|dt`%?r z?w>Uv{i=aZzy+wtzf7VLFabWl&_nxOgKYs1;Q5WDoL|5KIDLI!=q)Rm1uS29`u2^g zb9KD33djN1WA*bxZRh-w9m!hp&ub(6$8RmWK74o@Pe~jfIWGN@8J2$p($uSy^Z-Oe{2X znplZs2|)vrkZ4Rc5OMK~C}Ng<@9n-N$oT)AnLF>!y!n3fmh~lvJ8$m2=l?(Fo_p@c zK7wFNur)YzC{Q^YeD-J%JQ)PR>}>pfcMx3FyWJ|Of9<{?cv}q)sExV=M0+ z?%;}3@7z-+NuI&Kag38Q6OI}F*paGl%cEsKi-%u7PvRPXCOKw|o5m>1KhMUNTpEoZ zaiUl@zJt{fXZXaD>(@N~8*W4FNBModfcyILHM4Ilu8a6@qz&>t@t%qm&+qu3I@(n6 zr=9I7iss8SVyP?s;!G~GeGz+$4YNVwTgE_IUn6iD1+C+KP+dKL36Z2P>k)QH zcsA}{n^aya;!pcL8<6`0<$eA*Htnuj)NxsyM-S2+z5GcY4bBXKl#j1h*by zPvj%k{0?G?n8XgGDn6_DI~x=$7Mle&aF#G%RpE7FpXZ~R{HyXaw&mvMuK8y5b;Q4L zeaYf={k;xsWw0*X2MQhjST?IFBf9RmvbUOx({O^xF zxxRM3hx0-1vQ!?GbT|Hs+u0!h z>)d4b=gixT@0rWgjm}n;@jnswwl;8Xva^(L1J9gCI8)V&oldwnw?S*C<}f^obvFh) zkIsXY>uaanTiKx8$Xww3heWxbc)2&+o7*6+h;x#Vcjt${HB^x%B zd&f516Yd?^a8J2UXv3!9-qMCm#l4{oo05A?8#Xof3LE|x+%qYs34 zYA6?^hH^>WDWzZ+r4-CiO2HhZ6db2aWPVHZys$MYm$pUa@~)^H&P3(i<53yv6a7wW zHVA^JCCTr!^jog%M{)4F5pi_m{2gUwB@7X!@7+ zb9D)hFdcT~nJqf|Yv0?y_>#^bmvxuGZ+Q6o)D7Jg3!go4{vNXIJsisFM|CM|{_ZK; zk;(8!`pbA^u(5yj%6o=AHGI#XBcJR3UD$lb=U_7ia9KJ1NIhH1?DXouSpE6qaP8X3 zc)TNQVvn)*@fnBCIK;lT_~!Q!Cj6%0vd)achGQ0=IP8!Ec01fC&UHs7KP0j0;|n*L z4;^s;D~k`OTztgTc||*c%krFJ^_4UJH|NOIB|4&#ybgW+e$ewv$u7mrF7j$QG zJ>o1K=mZ-ZhBv-5-Nib*{?dUi+R}gRmy>p0HXVAO{9Y^TGx)3C`sz9A|B>mS&)T(5 zjURe?=MERxgP%se!hVEz{Y$TyE!e}e*Y0M~`1t+dfNv4c2Kt_ma9=E)zF_|dKdMXd zqYr%_h)Yigzl}I-elM^$=}SDS|Ms+wQhQzB?S3q0b(p;xKgQ6rH@C0TlhvW`zE4W~ sR_LhJw?IduzMhVq;(sibo$h^`a-REI-PJB}Kg-?hjP7OUqPx`p0MeJG$N&HU literal 0 HcmV?d00001 diff --git a/landing-page/public/static/images/logo_discord.png b/landing-page/public/static/images/logo_discord.png new file mode 100644 index 0000000000000000000000000000000000000000..24e567e1eba2331f522e0494f17b027b41a9b8af GIT binary patch literal 1298 zcmV+t1?~EYP){n&%S7qy0W$agF?NNaLS7q#1W$agF>{n&&S7q#1W$agF>{n&&S7q#0W$agF z?Nw#$S7q&2W$agF>{n*&S7hv0X6;vJ>{4XyS7q$~|NmNO^jT)?{QUl2YV2ES_FHJ| zR%Gm4Yy3@J>s)I2P+#mWNb2$N{ZnG>GD_+`QtLHL>-qWqMOf?g_Wjw~`J}1xa(nJ% zaP3}g|2IzS>g)U8;QF_^^sTY;VQuV5TI)Jc>mx$y)zihft=ji+8=K9Ug_q@RMv$yn^p7E5L@QRV|Zg}nF<@(3T_NlJ& zj+O9#hwd9c>HszBKve6>&G*U6_7OYk05%0ST5q2|{cmzP+;WVWJjRx8oZz+uf1_ z3VYjqNlGRYQB4g{3LuXM(|9E6nBMB zNZ3mvJflz|W~I8U%nU}99RY3i@f<>5(WbOG2Pb_&(Rsj#w2sD<$S?(N0mW=2qOyaQ z!c7H2ASc7CGXptiUkTkyWf`qFAg8EY(&Ub*l2@sNj;xhb8ema!2`wZz$RMp7W}#gc zA9N$5S;{njmc{aA!EEYjU_S7pA7oTMysl(LTeaI-_3Elxd%AYaY^7FL!3VDTK`NUm z+iPF+$-9v1axxT=Wb)z}_jcr3)9_-zOoSyP4(Nc5^5a9YS( zhJ-+BR6OvkebwXM`K$K62j;^dsNDnCGxdCwIr2JnY#ANCR!>6!q{gktT@Ok9$D_5s z^*Q}fCxjz$uIthW7E_l%Zj6oipIc>aGZ7W@N z!AFgRQmuGpfIniVVMSzzUc`JUkSc&&<~6=pHU9nOtMSiwZ}A}q0g%y@w|3AOW`>?? zVVF6(%X*6^X$*ca?IzN@m_os%PrvP4#yx zpKm>0-A24>3y?n}*fcoF$&N0VPV|d@S zz<@x$?+$-b5;*2U2P;sbs=$IE163t7e6WU(at44$1fji<6rX!|n15W^!t*Hd4GC*X zWcEzs0uo{gG$Y4wf6E>_cV`?sQKYzGkDY=LEutCF2rjO>qxEh=!S9-_EhjAXH~26g zTf>s1Oq=GD_ArljVh-Z|ec0NJt}U&uEKJhs(pq%$tL<*-AA?$8%T? + + diff --git a/landing-page/rollup.config.js b/landing-page/rollup.config.js new file mode 100644 index 000000000000..d316ad8aebba --- /dev/null +++ b/landing-page/rollup.config.js @@ -0,0 +1,10 @@ +import rollup from "../build-scripts/rollup.cjs"; +import env from "../build-scripts/env.cjs"; + +const config = rollup.createLandingPageConfig({ + isProdBuild: env.isProdBuild(), + latestBuild: true, + isStatsBuild: env.isStatsBuild(), +}); + +export default { ...config.inputOptions, output: config.outputOptions }; diff --git a/landing-page/script/build_landing_page b/landing-page/script/build_landing_page new file mode 100755 index 000000000000..e9e0102df8cd --- /dev/null +++ b/landing-page/script/build_landing_page @@ -0,0 +1,9 @@ +#!/bin/sh +# Run the landing-page + +# Stop on errors +set -e + +cd "$(dirname "$0")/../.." + +./node_modules/.bin/gulp build-landing-page diff --git a/landing-page/script/develop b/landing-page/script/develop new file mode 100755 index 000000000000..4ec2870060ba --- /dev/null +++ b/landing-page/script/develop @@ -0,0 +1,9 @@ +#!/bin/sh +# Run the landing-page + +# Stop on errors +set -e + +cd "$(dirname "$0")/../.." + +./node_modules/.bin/gulp develop-landing-page diff --git a/landing-page/src/entrypoint.js b/landing-page/src/entrypoint.js new file mode 100644 index 000000000000..4bf2a33b141a --- /dev/null +++ b/landing-page/src/entrypoint.js @@ -0,0 +1,3 @@ +import "./ha-landing-page"; + +import("../../src/resources/ha-style"); diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts new file mode 100644 index 000000000000..5e0bb1dc0cac --- /dev/null +++ b/landing-page/src/ha-landing-page.ts @@ -0,0 +1,125 @@ +import "@material/mwc-linear-progress/mwc-linear-progress"; +import "@material/mwc-top-app-bar-fixed"; +import { PropertyValues, css, html } from "lit"; +import { customElement, property } from "lit/decorators"; +import "../../src/components/ha-icon-button"; +import "../../src/managers/notification-manager"; +import { haStyle } from "../../src/resources/styles"; +import "../../src/onboarding/onboarding-welcome"; +import "../../src/onboarding/onboarding-welcome-links"; +import { litLocalizeLiteMixin } from "../../src/mixins/lit-localize-lite-mixin"; +import { HassElement } from "../../src/state/hass-element"; +import { extractSearchParam } from "../../src/common/url/search-params"; +import { storeState } from "../../src/util/ha-pref-storage"; + +@customElement("ha-landing-page") +class HaLandingPage extends litLocalizeLiteMixin(HassElement) { + @property() public translationFragment = "page-onboarding"; + + private _mobileApp = + extractSearchParam("redirect_uri") === "homeassistant://auth-callback"; + + render() { + return html` + +
+ +
+
+ + `; + } + + protected firstUpdated(changedProps: PropertyValues) { + super.firstUpdated(changedProps); + if (window.innerWidth > 450) { + import("../../src/resources/particles"); + } + import("../../src/components/ha-language-picker"); + } + + private _languageChanged(ev: CustomEvent) { + const language = ev.detail.value; + this.language = language; + if (this.hass) { + this._updateHass({ + locale: { ...this.hass!.locale, language }, + language, + selectedLanguage: language, + }); + storeState(this.hass!); + } else { + try { + localStorage.setItem("selectedLanguage", JSON.stringify(language)); + } catch (err: any) { + // Ignore + } + } + } + + static styles = [ + haStyle, + css` + mwc-linear-progress { + position: fixed; + top: 0; + left: 0; + width: 100%; + z-index: 10; + } + .footer { + padding-top: 8px; + display: flex; + justify-content: space-between; + align-items: center; + } + ha-language-picker { + display: block; + width: 200px; + border-radius: 4px; + overflow: hidden; + --ha-select-height: 40px; + --mdc-select-fill-color: none; + --mdc-select-label-ink-color: var(--primary-text-color, #212121); + --mdc-select-ink-color: var(--primary-text-color, #212121); + --mdc-select-idle-line-color: transparent; + --mdc-select-hover-line-color: transparent; + --mdc-select-dropdown-icon-color: var(--primary-text-color, #212121); + --mdc-shape-small: 0; + } + a { + text-decoration: none; + color: var(--primary-text-color); + margin-right: 16px; + margin-inline-end: 16px; + margin-inline-start: initial; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-landing-page": HaLandingPage; + } +} diff --git a/landing-page/src/html/index.html.template b/landing-page/src/html/index.html.template new file mode 100644 index 000000000000..2f2211c490da --- /dev/null +++ b/landing-page/src/html/index.html.template @@ -0,0 +1,53 @@ + + + + Home Assistant + <%= renderTemplate("../../../src/html/_header.html.template") %> + <%= renderTemplate("../../../src/html/_style_base.html.template") %> + + + +
+
+ Home Assistant +
+ +
+ <%= renderTemplate("../../../src/html/_js_base.html.template") %> + <%= renderTemplate("../../../src/html/_preload_roboto.html.template") %> + <%= renderTemplate("../../../src/html/_script_loader.html.template") %> + + diff --git a/landing-page/webpack.config.js b/landing-page/webpack.config.js new file mode 100644 index 000000000000..2e3152dcee38 --- /dev/null +++ b/landing-page/webpack.config.js @@ -0,0 +1,8 @@ +import webpack from "../build-scripts/webpack.cjs"; +import env from "../build-scripts/env.cjs"; + +export default webpack.createLandingPageConfig({ + isProdBuild: env.isProdBuild(), + isStatsBuild: env.isStatsBuild(), + latestBuild: true, +}); From 6ef7e79df598ef5fa291743b13496410998ee3dc Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 21 Oct 2024 14:14:29 +0200 Subject: [PATCH 02/33] Add landing-page-prepare --- .../src/components/landing-page-prepare.ts | 59 +++++++++++++++++++ landing-page/src/ha-landing-page.ts | 21 ++----- src/translations/en.json | 6 ++ 3 files changed, 71 insertions(+), 15 deletions(-) create mode 100644 landing-page/src/components/landing-page-prepare.ts diff --git a/landing-page/src/components/landing-page-prepare.ts b/landing-page/src/components/landing-page-prepare.ts new file mode 100644 index 000000000000..1eeea67d177f --- /dev/null +++ b/landing-page/src/components/landing-page-prepare.ts @@ -0,0 +1,59 @@ +import "@material/mwc-linear-progress/mwc-linear-progress"; +import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import type { LocalizeFunc } from "../../../src/common/translations/localize"; +import type { HomeAssistant } from "../../../src/types"; +import { onBoardingStyles } from "../../../src/onboarding/styles"; +import "../../../src/components/ha-button"; + +@customElement("landing-page-prepare") +class LandingPagePrepare extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public localize!: LocalizeFunc; + + @state() private _showDetails = false; + + protected render(): TemplateResult { + return html` +

${this.localize("ui.panel.page-onboarding.prepare.header")}

+

${this.localize("ui.panel.page-onboarding.prepare.subheader")}

+ + + + ${this.localize( + this._showDetails + ? "ui.panel.page-onboarding.prepare.hide_details" + : "ui.panel.page-onboarding.prepare.show_details" + )} + + `; + } + + private _toggleDetails(): void { + this._showDetails = !this._showDetails; + } + + static get styles(): CSSResultGroup { + return [ + onBoardingStyles, + css` + :host { + display: flex; + flex-direction: column; + align-items: center; + } + mwc-linear-progress { + width: 100%; + margin: 32px 0; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "landing-page-prepare": LandingPagePrepare; + } +} diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index 5e0bb1dc0cac..58028713c143 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -1,11 +1,9 @@ -import "@material/mwc-linear-progress/mwc-linear-progress"; -import "@material/mwc-top-app-bar-fixed"; import { PropertyValues, css, html } from "lit"; import { customElement, property } from "lit/decorators"; import "../../src/components/ha-icon-button"; import "../../src/managers/notification-manager"; import { haStyle } from "../../src/resources/styles"; -import "../../src/onboarding/onboarding-welcome"; +import "./components/landing-page-prepare"; import "../../src/onboarding/onboarding-welcome-links"; import { litLocalizeLiteMixin } from "../../src/mixins/lit-localize-lite-mixin"; import { HassElement } from "../../src/state/hass-element"; @@ -20,14 +18,13 @@ class HaLandingPage extends litLocalizeLiteMixin(HassElement) { extractSearchParam("redirect_uri") === "homeassistant://auth-callback"; render() { - return html` + return html`
- + >
${this.localize("ui.panel.page-onboarding.help")} - `; + + `; } protected firstUpdated(changedProps: PropertyValues) { @@ -80,13 +78,6 @@ class HaLandingPage extends litLocalizeLiteMixin(HassElement) { static styles = [ haStyle, css` - mwc-linear-progress { - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 10; - } .footer { padding-top: 8px; display: flex; diff --git a/src/translations/en.json b/src/translations/en.json index 8d775b4ce531..db79e8539d07 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7337,6 +7337,12 @@ "next": "Next", "finish": "Finish", "help": "Help", + "prepare": { + "header": "Preparing Home Assistant", + "subheader": "This may take 20 minutes or more", + "show_details": "Show details", + "hide_details": "Hide details" + }, "welcome": { "header": "Welcome!", "start": "Create my smart home", From 3e8d7ef40af442295372d40861c402e5c36da376 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 22 Oct 2024 15:24:05 +0200 Subject: [PATCH 03/33] Add landing-page-network --- .../src/components/landing-page-network.ts | 174 ++++++++++++++++++ .../src/components/landing-page-prepare.ts | 59 ------ landing-page/src/ha-landing-page.ts | 94 +++++++--- src/translations/en.json | 11 +- 4 files changed, 256 insertions(+), 82 deletions(-) create mode 100644 landing-page/src/components/landing-page-network.ts delete mode 100644 landing-page/src/components/landing-page-prepare.ts diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts new file mode 100644 index 000000000000..9260e947ce8f --- /dev/null +++ b/landing-page/src/components/landing-page-network.ts @@ -0,0 +1,174 @@ +import "@material/mwc-linear-progress/mwc-linear-progress"; +import { + CSSResultGroup, + LitElement, + PropertyValues, + css, + html, + nothing, +} from "lit"; +import { customElement, property, state } from "lit/decorators"; +import type { + LocalizeFunc, + LocalizeKeys, +} from "../../../src/common/translations/localize"; +import "../../../src/components/ha-button"; +import "../../../src/components/ha-alert"; +import { fireEvent } from "../../../src/common/dom/fire_event"; + +const ALTERNATIVE_DNS_SERVERS: { + ipv4: string[]; + ipv6: string[]; + translationKey: LocalizeKeys; +}[] = [ + { + ipv4: ["1.1.1.1", "1.0.0.1"], + ipv6: ["2606:4700:4700::1111", "2606:4700:4700::1001"], + translationKey: + "ui.panel.page-onboarding.prepare.network_issue.use_cloudflare", + }, + { + ipv4: ["8.8.8.8", "8.8.4.4"], + ipv6: ["2001:4860:4860::8888", "2001:4860:4860::8844"], + translationKey: "ui.panel.page-onboarding.prepare.network_issue.use_google", + }, +]; + +@customElement("landing-page-network") +class LandingPageNetwork extends LitElement { + @property({ attribute: false }) public localize!: LocalizeFunc; + + @state() private _networkIssue = false; + + @state() private _getNetworkInfoError = false; + + @state() private _dnsPrimaryInterface?: string; + + protected render() { + if (!this._networkIssue && !this._getNetworkInfoError) { + return nothing; + } + + console.log(this._getNetworkInfoError); + + if (this._getNetworkInfoError) { + return html` + +

+ ${this.localize( + "ui.panel.page-onboarding.prepare.network_issue.error_get_network_info" + )} +

+
+ `; + } + + return html` + +

+ ${this.localize( + "ui.panel.page-onboarding.prepare.network_issue.description", + { dns: this._dnsPrimaryInterface || "?" } + )} +

+

+ ${this.localize( + "ui.panel.page-onboarding.prepare.network_issue.resolve_different" + )} +

+ ${!this._dnsPrimaryInterface + ? html` +

+ ${this.localize( + "ui.panel.page-onboarding.prepare.network_issue.no_primary_interface" + )} + +

+ ` + : nothing} +
+ ${ALTERNATIVE_DNS_SERVERS.map( + ({ translationKey }, key) => + html`${this.localize(translationKey)}` + )} +
+
+ `; + } + + protected firstUpdated(_changedProperties: PropertyValues): void { + super.firstUpdated(_changedProperties); + // this._fetchSupervisorInfo(); + } + + private async _fetchSupervisorInfo() { + try { + const response = await fetch("/supervisor/network/info"); + if (!response.ok) { + throw new Error("Failed to fetch network info"); + } + + const { data } = await response.json(); + + if (!data.data.host_internet) { + this._networkIssue = true; + const primaryInterface = data.interfaces.find( + (intf) => intf.primary && intf.enabled + ); + if (primaryInterface) { + this._dnsPrimaryInterface = [ + ...(primaryInterface.ipv4?.nameservers || []), + ...(primaryInterface.ipv6?.nameservers || []), + ].join(", "); + } + } else { + this._networkIssue = false; + } + } catch (err) { + console.error(err); + this._getNetworkInfoError = true; + } + + fireEvent(this, "value-changed", { + value: this._networkIssue || this._getNetworkInfoError, + }); + } + + private async _setDns(ev) { + const index = ev.target?.index; + try { + const response = await fetch("/supervisor/network/dns", { + method: "POST", + body: JSON.stringify(ALTERNATIVE_DNS_SERVERS[index]), + }); + if (!response.ok) { + throw new Error("Failed to set DNS"); + } + this._networkIssue = false; + } catch (err) { + console.error(err); + this._getNetworkInfoError = true; + } + } + + static get styles(): CSSResultGroup { + return [css``]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "landing-page-network": LandingPageNetwork; + } +} diff --git a/landing-page/src/components/landing-page-prepare.ts b/landing-page/src/components/landing-page-prepare.ts deleted file mode 100644 index 1eeea67d177f..000000000000 --- a/landing-page/src/components/landing-page-prepare.ts +++ /dev/null @@ -1,59 +0,0 @@ -import "@material/mwc-linear-progress/mwc-linear-progress"; -import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import type { LocalizeFunc } from "../../../src/common/translations/localize"; -import type { HomeAssistant } from "../../../src/types"; -import { onBoardingStyles } from "../../../src/onboarding/styles"; -import "../../../src/components/ha-button"; - -@customElement("landing-page-prepare") -class LandingPagePrepare extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ attribute: false }) public localize!: LocalizeFunc; - - @state() private _showDetails = false; - - protected render(): TemplateResult { - return html` -

${this.localize("ui.panel.page-onboarding.prepare.header")}

-

${this.localize("ui.panel.page-onboarding.prepare.subheader")}

- - - - ${this.localize( - this._showDetails - ? "ui.panel.page-onboarding.prepare.hide_details" - : "ui.panel.page-onboarding.prepare.show_details" - )} - - `; - } - - private _toggleDetails(): void { - this._showDetails = !this._showDetails; - } - - static get styles(): CSSResultGroup { - return [ - onBoardingStyles, - css` - :host { - display: flex; - flex-direction: column; - align-items: center; - } - mwc-linear-progress { - width: 100%; - margin: 32px 0; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "landing-page-prepare": LandingPagePrepare; - } -} diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index 58028713c143..16295c265e3e 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -1,19 +1,25 @@ -import { PropertyValues, css, html } from "lit"; -import { customElement, property } from "lit/decorators"; -import "../../src/components/ha-icon-button"; -import "../../src/managers/notification-manager"; +import "@material/mwc-linear-progress"; +import { PropertyValues, css, html, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../src/components/ha-alert"; import { haStyle } from "../../src/resources/styles"; -import "./components/landing-page-prepare"; import "../../src/onboarding/onboarding-welcome-links"; +import "./components/landing-page-network"; import { litLocalizeLiteMixin } from "../../src/mixins/lit-localize-lite-mixin"; import { HassElement } from "../../src/state/hass-element"; import { extractSearchParam } from "../../src/common/url/search-params"; -import { storeState } from "../../src/util/ha-pref-storage"; +import { onBoardingStyles } from "../../src/onboarding/styles"; @customElement("ha-landing-page") class HaLandingPage extends litLocalizeLiteMixin(HassElement) { @property() public translationFragment = "page-onboarding"; + @state() private _networkIssue = false; + + @state() private _supervisorError = false; + + @state() private _logDetails = false; + private _mobileApp = extractSearchParam("redirect_uri") === "homeassistant://auth-callback"; @@ -21,10 +27,40 @@ class HaLandingPage extends litLocalizeLiteMixin(HassElement) { return html`
- ${this.localize("ui.panel.page-onboarding.prepare.header")} + ${!this._networkIssue && !this._supervisorError + ? html` +

+ ${this.localize("ui.panel.page-onboarding.prepare.subheader")} +

+ + ` + : nothing} +
+ > + + ${this._supervisorError + ? html` + + An error occured while installing Home Assistant, check the + logs below for more information. + + ` + : nothing} +
+ + ${this.localize( + this._logDetails + ? "ui.panel.page-onboarding.prepare.hide_details" + : "ui.panel.page-onboarding.prepare.show_details" + )} + +
Date: Wed, 23 Oct 2024 15:17:14 +0200 Subject: [PATCH 04/33] Add logs to landing-page --- landing-page/.eslintrc.json | 5 +- .../src/components/landing-page-logs.ts | 233 ++++++++++++++++++ .../src/components/landing-page-network.ts | 35 ++- landing-page/src/data/mockFetch.ts | 3 + landing-page/src/data/supervisor.ts | 9 + landing-page/src/ha-landing-page.ts | 37 +-- src/translations/en.json | 5 + 7 files changed, 303 insertions(+), 24 deletions(-) create mode 100644 landing-page/src/components/landing-page-logs.ts create mode 100644 landing-page/src/data/mockFetch.ts create mode 100644 landing-page/src/data/supervisor.ts diff --git a/landing-page/.eslintrc.json b/landing-page/.eslintrc.json index 59c30e3b6d13..659ccca5615d 100644 --- a/landing-page/.eslintrc.json +++ b/landing-page/.eslintrc.json @@ -1,6 +1,3 @@ { - "extends": "../.eslintrc.json", - "rules": { - "no-console": 0 - } + "extends": "../.eslintrc.json" } diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts new file mode 100644 index 000000000000..9c89a91a4763 --- /dev/null +++ b/landing-page/src/components/landing-page-logs.ts @@ -0,0 +1,233 @@ +import "@material/mwc-linear-progress/mwc-linear-progress"; +import { mdiArrowCollapseDown } from "@mdi/js"; +// eslint-disable-next-line import/extensions +import { IntersectionController } from "@lit-labs/observers/intersection-controller.js"; +import { LitElement, PropertyValues, css, html, nothing } from "lit"; +import { classMap } from "lit/directives/class-map"; +import { customElement, property, query, state } from "lit/decorators"; +import type { LocalizeFunc } from "../../../src/common/translations/localize"; +import "../../../src/components/ha-button"; +import "../../../src/components/ha-svg-icon"; +import "../../../src/components/ha-ansi-to-html"; +import "../../../src/components/ha-alert"; +import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; +import { getObserverLogsFollow } from "../data/supervisor"; + +@customElement("landing-page-logs") +class LandingPageLogs extends LitElement { + @property({ attribute: false }) public localize!: LocalizeFunc; + + @query("ha-ansi-to-html") private _ansiToHtmlElement?: HaAnsiToHtml; + + @query(".logs") private _logElement?: HTMLElement; + + @query("#scroll-bottom-marker") + private _scrollBottomMarkerElement?: HTMLElement; + + @state() private _show = false; + + @state() private _scrolledToBottomController = + new IntersectionController(this, { + callback(this: IntersectionController, entries) { + return entries[0].isIntersecting; + }, + }); + + @state() private _error = false; + + @state() private _newLogsIndicator?: boolean; + + protected render() { + return html` + + ${this.localize( + this._show + ? "ui.panel.page-onboarding.prepare.hide_details" + : "ui.panel.page-onboarding.prepare.show_details" + )} + +
+ ${this._error + ? html` + + + ${this.localize( + "ui.panel.page-onboarding.prepare.logs.retry" + )} + + + ` + : nothing} + +
+
+ + + ${this.localize( + "ui.panel.page-onboarding.prepare.logs.scroll_down_button" + )} + + + `; + } + + protected firstUpdated(changedProps: PropertyValues): void { + super.firstUpdated(changedProps); + + this._scrolledToBottomController.observe(this._scrollBottomMarkerElement!); + + this._startLogStream(); + } + + protected updated(changedProps: PropertyValues): void { + super.updated(changedProps); + + if (this._newLogsIndicator && this._scrolledToBottomController.value) { + this._newLogsIndicator = false; + } + + if (changedProps.has("_show") && this._show) { + this._scrollToBottom(); + } + } + + private _toggleLogDetails() { + this._show = !this._show; + } + + private _scrollToBottom(): void { + if (this._logElement) { + this._newLogsIndicator = false; + this._logElement!.scrollTo(0, this._logElement!.scrollHeight); + } + } + + private async _startLogStream() { + this._error = false; + this._newLogsIndicator = false; + this._ansiToHtmlElement?.clear(); + + try { + const response = await getObserverLogsFollow(); + + if (!response.ok || !response.body) { + throw new Error("No stream body found"); + } + + let tempLogLine = ""; + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let done = false; + + while (!done) { + // eslint-disable-next-line no-await-in-loop + const { value, done: readerDone } = await reader.read(); + done = readerDone; + + if (value) { + const chunk = decoder.decode(value, { stream: !done }); + const scrolledToBottom = this._scrolledToBottomController.value; + const lines = `${tempLogLine}${chunk}` + .split("\n") + .filter((line) => line.trim() !== ""); + + // handle edge case where the last line is not complete + if (chunk.endsWith("\n")) { + tempLogLine = ""; + } else { + tempLogLine = lines.splice(-1, 1)[0]; + } + + if (lines.length) { + this._ansiToHtmlElement?.parseLinesToColoredPre(lines); + } + + if (scrolledToBottom && this._logElement) { + this._scrollToBottom(); + } else { + this._newLogsIndicator = true; + } + } + } + } catch (err: any) { + // eslint-disable-next-line no-console + console.error(err); + this._error = true; + } + } + + static styles = [ + css` + :host { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + } + + .logs { + width: 100%; + max-height: 300px; + overflow: auto; + border: 1px solid var(--divider-color); + border-radius: 4px; + padding: 4px; + } + + .logs.hidden { + display: none; + } + + .new-logs-indicator { + --mdc-theme-primary: var(--text-primary-color); + + overflow: hidden; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 0; + background-color: var(--primary-color); + border-radius: 8px; + + transition: height 0.4s ease-out; + display: flex; + justify-content: space-between; + align-items: center; + } + + .new-logs-indicator.visible { + height: 24px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "landing-page-logs": LandingPageLogs; + } +} diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts index 9260e947ce8f..63a20568ef14 100644 --- a/landing-page/src/components/landing-page-network.ts +++ b/landing-page/src/components/landing-page-network.ts @@ -14,8 +14,11 @@ import type { } from "../../../src/common/translations/localize"; import "../../../src/components/ha-button"; import "../../../src/components/ha-alert"; +import { getSupervisorNetworkInfo } from "../data/supervisor"; import { fireEvent } from "../../../src/common/dom/fire_event"; +const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5; + const ALTERNATIVE_DNS_SERVERS: { ipv4: string[]; ipv6: string[]; @@ -49,8 +52,6 @@ class LandingPageNetwork extends LitElement { return nothing; } - console.log(this._getNetworkInfoError); - if (this._getNetworkInfoError) { return html` @@ -109,19 +110,26 @@ class LandingPageNetwork extends LitElement { protected firstUpdated(_changedProperties: PropertyValues): void { super.firstUpdated(_changedProperties); - // this._fetchSupervisorInfo(); + this._fetchSupervisorInfo(); + } + + private _scheduleFetchSupervisorInfo() { + setTimeout( + () => this._fetchSupervisorInfo(), + SCHEDULE_FETCH_NETWORK_INFO_SECONDS * 1000 + ); } private async _fetchSupervisorInfo() { try { - const response = await fetch("/supervisor/network/info"); + const response = await getSupervisorNetworkInfo(); if (!response.ok) { throw new Error("Failed to fetch network info"); } const { data } = await response.json(); - if (!data.data.host_internet) { + if (!data.host_internet) { this._networkIssue = true; const primaryInterface = data.interfaces.find( (intf) => intf.primary && intf.enabled @@ -136,6 +144,7 @@ class LandingPageNetwork extends LitElement { this._networkIssue = false; } } catch (err) { + // eslint-disable-next-line no-console console.error(err); this._getNetworkInfoError = true; } @@ -143,6 +152,7 @@ class LandingPageNetwork extends LitElement { fireEvent(this, "value-changed", { value: this._networkIssue || this._getNetworkInfoError, }); + this._scheduleFetchSupervisorInfo(); } private async _setDns(ev) { @@ -150,13 +160,26 @@ class LandingPageNetwork extends LitElement { try { const response = await fetch("/supervisor/network/dns", { method: "POST", - body: JSON.stringify(ALTERNATIVE_DNS_SERVERS[index]), + body: JSON.stringify({ + ipv4: { + method: "auto", + nameservers: ALTERNATIVE_DNS_SERVERS[index].ipv4, + }, + ipv6: { + method: "auto", + nameservers: ALTERNATIVE_DNS_SERVERS[index].ipv6, + }, + }), + headers: { + "Content-Type": "application/json", + }, }); if (!response.ok) { throw new Error("Failed to set DNS"); } this._networkIssue = false; } catch (err) { + // eslint-disable-next-line no-console console.error(err); this._getNetworkInfoError = true; } diff --git a/landing-page/src/data/mockFetch.ts b/landing-page/src/data/mockFetch.ts new file mode 100644 index 000000000000..3d99221b720f --- /dev/null +++ b/landing-page/src/data/mockFetch.ts @@ -0,0 +1,3 @@ +const mockFetch = async (url: string) => fetch(`http://localhost:3000${url}`); + +export default mockFetch; diff --git a/landing-page/src/data/supervisor.ts b/landing-page/src/data/supervisor.ts new file mode 100644 index 000000000000..741e4a830d7f --- /dev/null +++ b/landing-page/src/data/supervisor.ts @@ -0,0 +1,9 @@ +import fetch from "./mockFetch"; + +export async function getObserverLogsFollow() { + return fetch("/observer/logs/follow?lines=500"); +} + +export async function getSupervisorNetworkInfo() { + return fetch("/supervisor/network/info"); +} diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index 16295c265e3e..34adbccb9f02 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -5,11 +5,14 @@ import "../../src/components/ha-alert"; import { haStyle } from "../../src/resources/styles"; import "../../src/onboarding/onboarding-welcome-links"; import "./components/landing-page-network"; +import "./components/landing-page-logs"; import { litLocalizeLiteMixin } from "../../src/mixins/lit-localize-lite-mixin"; import { HassElement } from "../../src/state/hass-element"; import { extractSearchParam } from "../../src/common/url/search-params"; import { onBoardingStyles } from "../../src/onboarding/styles"; +const SCHEDULE_CORE_CHECK_SECONDS = 5; + @customElement("ha-landing-page") class HaLandingPage extends litLocalizeLiteMixin(HassElement) { @property() public translationFragment = "page-onboarding"; @@ -18,8 +21,6 @@ class HaLandingPage extends litLocalizeLiteMixin(HassElement) { @state() private _supervisorError = false; - @state() private _logDetails = false; - private _mobileApp = extractSearchParam("redirect_uri") === "homeassistant://auth-callback"; @@ -52,15 +53,7 @@ class HaLandingPage extends litLocalizeLiteMixin(HassElement) { ` : nothing} -
- - ${this.localize( - this._logDetails - ? "ui.panel.page-onboarding.prepare.hide_details" - : "ui.panel.page-onboarding.prepare.show_details" - )} - -
+ this._checkCoreAvailability(), + SCHEDULE_CORE_CHECK_SECONDS * 1000 + ); } - private _toggleLogDetails() { - this._logDetails = !this._logDetails; + private async _checkCoreAvailability() { + try { + const response = await fetch("/manifest.json"); + if (response.ok) { + location.reload(); + } + } finally { + this._scheduleCoreCheck(); + } } private _networkInfoChanged(ev: CustomEvent) { @@ -105,7 +114,7 @@ class HaLandingPage extends litLocalizeLiteMixin(HassElement) { this.language = language; try { localStorage.setItem("selectedLanguage", JSON.stringify(language)); - window.location.reload(); + location.reload(); } catch (err: any) { // Ignore } diff --git a/src/translations/en.json b/src/translations/en.json index 9164eeae4b12..ade0eb7ab233 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7350,6 +7350,11 @@ "use_cloudflare": "Use Cloudflare DNS", "use_google": "Use Google DNS", "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!" + }, + "logs": { + "scroll_down_button": "New logs - Click to scroll", + "fetch_error": "Failed to fetch logs", + "retry": "Retry" } }, "welcome": { From a6627286e9a3a242b38708d8a7e05179273df3d3 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 23 Oct 2024 15:52:47 +0200 Subject: [PATCH 05/33] Improve landing-page build --- build-scripts/gulp/gather-static.js | 15 +++++++++++---- landing-page/src/ha-landing-page.ts | 15 +++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index 12495235377e..2899e78b001c 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -27,6 +27,16 @@ function copyTranslations(staticDir) { ); } +function copyFragmentTranslations(staticDir, pageName) { + const staticPath = genStaticPath(staticDir); + + // Translation output + fs.copySync( + polyPath(`build/translations/output/${pageName}`), + staticPath(`translations/${pageName}`) + ); +} + function copyLocaleData(staticDir) { const staticPath = genStaticPath(staticDir); @@ -209,9 +219,6 @@ gulp.task("copy-static-landing-page", async () => { paths.landingPage_output_root ); - copyMapPanel(paths.landingPage_output_static); copyFonts(paths.landingPage_output_static); - copyTranslations(paths.landingPage_output_static); - copyLocaleData(paths.landingPage_output_static); - copyMdiIcons(paths.landingPage_output_static); + copyFragmentTranslations(paths.landingPage_output_static, "page-onboarding"); }); diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index 34adbccb9f02..f5236d3887a0 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -1,5 +1,5 @@ import "@material/mwc-linear-progress"; -import { PropertyValues, css, html, nothing } from "lit"; +import { LitElement, type PropertyValues, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import "../../src/components/ha-alert"; import { haStyle } from "../../src/resources/styles"; @@ -7,15 +7,22 @@ import "../../src/onboarding/onboarding-welcome-links"; import "./components/landing-page-network"; import "./components/landing-page-logs"; import { litLocalizeLiteMixin } from "../../src/mixins/lit-localize-lite-mixin"; -import { HassElement } from "../../src/state/hass-element"; import { extractSearchParam } from "../../src/common/url/search-params"; import { onBoardingStyles } from "../../src/onboarding/styles"; +import type { Constructor } from "../../src/types"; +import themesMixin from "../../src/state/themes-mixin"; const SCHEDULE_CORE_CHECK_SECONDS = 5; +const ext = (baseClass: T, mixins): T => + mixins.reduceRight((base, mixin) => mixin(base), baseClass); + @customElement("ha-landing-page") -class HaLandingPage extends litLocalizeLiteMixin(HassElement) { - @property() public translationFragment = "page-onboarding"; +class HaLandingPage extends litLocalizeLiteMixin( + ext(LitElement, [themesMixin]) +) { + @property({ attribute: false }) public translationFragment = + "page-onboarding"; @state() private _networkIssue = false; From 677956e85dc4a7e6a8af09edd0a116805fa6e884 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Thu, 24 Oct 2024 14:54:55 +0200 Subject: [PATCH 06/33] Improve landing-page logs and detail dialogs --- build-scripts/gulp/webpack.js | 12 ++ landing-page/public/static/icons/logo_ohf.svg | 3 + landing-page/public/static/icons/ohf.svg | 1 + .../public/static/images/appstore.svg | 1 + .../public/static/images/logo_mastodon.svg | 10 ++ .../public/static/images/playstore.svg | 1 + .../public/static/images/qr-appstore.svg | 1 + .../public/static/images/qr-playstore.svg | 1 + .../src/components/landing-page-logs.ts | 132 ++++++++++++------ .../src/components/landing-page-network.ts | 11 +- landing-page/src/data/mockFetch.ts | 3 - landing-page/src/data/supervisor.ts | 8 +- landing-page/src/ha-landing-page.ts | 30 ++-- public/static/icons/logo_ohf.svg | 3 + public/static/images/logo_mastodon.svg | 10 ++ src/onboarding/dialogs/app-dialog.ts | 6 +- src/onboarding/dialogs/community-dialog.ts | 34 +++-- src/translations/en.json | 5 +- 18 files changed, 199 insertions(+), 73 deletions(-) create mode 100644 landing-page/public/static/icons/logo_ohf.svg create mode 100644 landing-page/public/static/icons/ohf.svg create mode 100644 landing-page/public/static/images/appstore.svg create mode 100644 landing-page/public/static/images/logo_mastodon.svg create mode 100644 landing-page/public/static/images/playstore.svg create mode 100644 landing-page/public/static/images/qr-appstore.svg create mode 100644 landing-page/public/static/images/qr-playstore.svg delete mode 100644 landing-page/src/data/mockFetch.ts create mode 100644 public/static/icons/logo_ohf.svg create mode 100644 public/static/images/logo_mastodon.svg diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index a87bb4aa51d2..ff6feed8ed12 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -42,6 +42,7 @@ const runDevServer = async ({ contentBase, port, listenHost = undefined, + proxy = undefined, }) => { if (listenHost === undefined) { // For dev container, we need to listen on all hosts @@ -57,6 +58,7 @@ const runDevServer = async ({ directory: contentBase, watch: true, }, + proxy, }, compiler ); @@ -209,6 +211,16 @@ gulp.task("webpack-dev-server-landing-page", () => contentBase: paths.landingPage_output_root, port: 8110, listenHost: "0.0.0.0", + proxy: [ + { + context: ["/observer"], + target: "http://localhost:3000", + }, + { + context: ["/supervisor"], + target: "http://localhost:3000", + }, + ], }) ); diff --git a/landing-page/public/static/icons/logo_ohf.svg b/landing-page/public/static/icons/logo_ohf.svg new file mode 100644 index 000000000000..151a7a82733c --- /dev/null +++ b/landing-page/public/static/icons/logo_ohf.svg @@ -0,0 +1,3 @@ + + + diff --git a/landing-page/public/static/icons/ohf.svg b/landing-page/public/static/icons/ohf.svg new file mode 100644 index 000000000000..b6ef1172ca95 --- /dev/null +++ b/landing-page/public/static/icons/ohf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/landing-page/public/static/images/appstore.svg b/landing-page/public/static/images/appstore.svg new file mode 100644 index 000000000000..da50ed4ce73e --- /dev/null +++ b/landing-page/public/static/images/appstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/landing-page/public/static/images/logo_mastodon.svg b/landing-page/public/static/images/logo_mastodon.svg new file mode 100644 index 000000000000..0f8baebfc9cb --- /dev/null +++ b/landing-page/public/static/images/logo_mastodon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/landing-page/public/static/images/playstore.svg b/landing-page/public/static/images/playstore.svg new file mode 100644 index 000000000000..7528a3d650cb --- /dev/null +++ b/landing-page/public/static/images/playstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/landing-page/public/static/images/qr-appstore.svg b/landing-page/public/static/images/qr-appstore.svg new file mode 100644 index 000000000000..3c6605bb8d1d --- /dev/null +++ b/landing-page/public/static/images/qr-appstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/landing-page/public/static/images/qr-playstore.svg b/landing-page/public/static/images/qr-playstore.svg new file mode 100644 index 000000000000..af088506ae9f --- /dev/null +++ b/landing-page/public/static/images/qr-playstore.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index 9c89a91a4763..a95f23e8cd1a 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -11,7 +11,15 @@ import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-ansi-to-html"; import "../../../src/components/ha-alert"; import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; -import { getObserverLogsFollow } from "../data/supervisor"; +import { getObserverLogs, getSupervisorLogsFollow } from "../data/supervisor"; +import { fireEvent } from "../../../src/common/dom/fire_event"; + +const ERROR_CHECK = /^[\d -:]+(ERROR|CRITICAL)(.*)/gm; +declare global { + interface HASSDomEvents { + "landing-page-error": undefined; + } +} @customElement("landing-page-logs") class LandingPageLogs extends LitElement { @@ -37,6 +45,8 @@ class LandingPageLogs extends LitElement { @state() private _newLogsIndicator?: boolean; + @state() private _observerLogs = ""; + protected render() { return html` @@ -46,28 +56,26 @@ class LandingPageLogs extends LitElement { : "ui.panel.page-onboarding.prepare.show_details" )} + ${this._error + ? html` + + + ${this.localize("ui.panel.page-onboarding.prepare.logs.retry")} + + + ` + : nothing}
- ${this._error - ? html` - - - ${this.localize( - "ui.panel.page-onboarding.prepare.logs.retry" - )} - - - ` - : nothing}
@@ -124,13 +132,46 @@ class LandingPageLogs extends LitElement { } } + private _writeChunk(chunk: string, tempLogLine = ""): string { + const showError = ERROR_CHECK.test(chunk); + + const scrolledToBottom = this._scrolledToBottomController.value; + const lines = `${tempLogLine}${chunk}` + .split("\n") + .filter((line) => line.trim() !== ""); + + // handle edge case where the last line is not complete + if (chunk.endsWith("\n")) { + tempLogLine = ""; + } else { + tempLogLine = lines.splice(-1, 1)[0]; + } + + if (lines.length) { + this._ansiToHtmlElement?.parseLinesToColoredPre(lines); + } + + if (showError) { + fireEvent(this, "landing-page-error"); + this._show = true; + } + + if (showError || (scrolledToBottom && this._logElement)) { + this._scrollToBottom(); + } else { + this._newLogsIndicator = true; + } + + return tempLogLine; + } + private async _startLogStream() { this._error = false; this._newLogsIndicator = false; this._ansiToHtmlElement?.clear(); try { - const response = await getObserverLogsFollow(); + const response = await getSupervisorLogsFollow(); if (!response.ok || !response.body) { throw new Error("No stream body found"); @@ -149,32 +190,41 @@ class LandingPageLogs extends LitElement { if (value) { const chunk = decoder.decode(value, { stream: !done }); - const scrolledToBottom = this._scrolledToBottomController.value; - const lines = `${tempLogLine}${chunk}` - .split("\n") - .filter((line) => line.trim() !== ""); - - // handle edge case where the last line is not complete - if (chunk.endsWith("\n")) { - tempLogLine = ""; - } else { - tempLogLine = lines.splice(-1, 1)[0]; - } - - if (lines.length) { - this._ansiToHtmlElement?.parseLinesToColoredPre(lines); - } - - if (scrolledToBottom && this._logElement) { - this._scrollToBottom(); - } else { - this._newLogsIndicator = true; - } + tempLogLine = this._writeChunk(chunk, tempLogLine); } } } catch (err: any) { // eslint-disable-next-line no-console console.error(err); + + // fallback to observerlogs if there is a problem with supervisor + this._loadObserverLogs(); + } + } + + private _scheduleObserverLogs() { + setTimeout(() => this._loadObserverLogs(), 5000); + } + + private async _loadObserverLogs() { + try { + const response = await getObserverLogs(); + + if (!response.ok) { + throw new Error("Error fetching observer logs"); + } + + const logs = await response.text(); + + if (this._observerLogs !== logs) { + this._observerLogs = logs; + this._writeChunk(logs); + } + + this._scheduleObserverLogs(); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); this._error = true; } } @@ -188,6 +238,10 @@ class LandingPageLogs extends LitElement { position: relative; } + ha-alert { + width: 100%; + } + .logs { width: 100%; max-height: 300px; diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts index 63a20568ef14..1fd959e2788f 100644 --- a/landing-page/src/components/landing-page-network.ts +++ b/landing-page/src/components/landing-page-network.ts @@ -129,6 +129,8 @@ class LandingPageNetwork extends LitElement { const { data } = await response.json(); + this._getNetworkInfoError = false; + if (!data.host_internet) { this._networkIssue = true; const primaryInterface = data.interfaces.find( @@ -186,7 +188,14 @@ class LandingPageNetwork extends LitElement { } static get styles(): CSSResultGroup { - return [css``]; + return [ + css` + .actions { + display: flex; + justify-content: flex-end; + } + `, + ]; } } diff --git a/landing-page/src/data/mockFetch.ts b/landing-page/src/data/mockFetch.ts deleted file mode 100644 index 3d99221b720f..000000000000 --- a/landing-page/src/data/mockFetch.ts +++ /dev/null @@ -1,3 +0,0 @@ -const mockFetch = async (url: string) => fetch(`http://localhost:3000${url}`); - -export default mockFetch; diff --git a/landing-page/src/data/supervisor.ts b/landing-page/src/data/supervisor.ts index 741e4a830d7f..58cfebdcf223 100644 --- a/landing-page/src/data/supervisor.ts +++ b/landing-page/src/data/supervisor.ts @@ -1,7 +1,9 @@ -import fetch from "./mockFetch"; +export async function getSupervisorLogsFollow() { + return fetch("/supervisor/logs/follow?lines=500"); +} -export async function getObserverLogsFollow() { - return fetch("/observer/logs/follow?lines=500"); +export async function getObserverLogs() { + return fetch("/observer/logs"); } export async function getSupervisorNetworkInfo() { diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index f5236d3887a0..5a30a556655e 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -11,6 +11,8 @@ import { extractSearchParam } from "../../src/common/url/search-params"; import { onBoardingStyles } from "../../src/onboarding/styles"; import type { Constructor } from "../../src/types"; import themesMixin from "../../src/state/themes-mixin"; +import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; +import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin"; const SCHEDULE_CORE_CHECK_SECONDS = 5; @@ -19,7 +21,7 @@ const ext = (baseClass: T, mixins): T => @customElement("ha-landing-page") class HaLandingPage extends litLocalizeLiteMixin( - ext(LitElement, [themesMixin]) + ext(ProvideHassLitMixin(LitElement), [themesMixin]) ) { @property({ attribute: false }) public translationFragment = "page-onboarding"; @@ -53,14 +55,20 @@ class HaLandingPage extends litLocalizeLiteMixin( ? html` - An error occured while installing Home Assistant, check the - logs below for more information. + ${this.localize( + "ui.panel.page-onboarding.prepare.error_description" + )} ` : nothing} - + 450) { import("../../src/resources/particles"); } @@ -112,6 +123,10 @@ class HaLandingPage extends litLocalizeLiteMixin( } } + private _showError() { + this._supervisorError = true; + } + private _networkInfoChanged(ev: CustomEvent) { this._networkIssue = ev.detail.value; } @@ -145,11 +160,6 @@ class HaLandingPage extends litLocalizeLiteMixin( ha-alert p { text-align: unset; } - .actions { - display: flex; - justify-content: flex-end; - margin-top: 16px; - } ha-language-picker { display: block; width: 200px; diff --git a/public/static/icons/logo_ohf.svg b/public/static/icons/logo_ohf.svg new file mode 100644 index 000000000000..151a7a82733c --- /dev/null +++ b/public/static/icons/logo_ohf.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/static/images/logo_mastodon.svg b/public/static/images/logo_mastodon.svg new file mode 100644 index 000000000000..0f8baebfc9cb --- /dev/null +++ b/public/static/images/logo_mastodon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/onboarding/dialogs/app-dialog.ts b/src/onboarding/dialogs/app-dialog.ts index d5de77408690..5be88e04861b 100644 --- a/src/onboarding/dialogs/app-dialog.ts +++ b/src/onboarding/dialogs/app-dialog.ts @@ -77,18 +77,16 @@ class DialogApp extends LitElement { --mdc-dialog-min-width: min(500px, 90vw); } .app-qr { - margin: 24px auto 0 auto; display: flex; justify-content: space-between; - padding: 0 24px; box-sizing: border-box; - gap: 16px; + gap: 32px; width: 100%; - max-width: 400px; } .app-qr a, .app-qr img { flex: 1; + max-width: 180px; } `; } diff --git a/src/onboarding/dialogs/community-dialog.ts b/src/onboarding/dialogs/community-dialog.ts index 7df13b7318a0..629349676c1b 100644 --- a/src/onboarding/dialogs/community-dialog.ts +++ b/src/onboarding/dialogs/community-dialog.ts @@ -40,7 +40,11 @@ class DialogCommunity extends LitElement { href="https://community.home-assistant.io/" > - + Home Assistant Logo ${this.localize("ui.panel.page-onboarding.welcome.forums")} @@ -51,7 +55,11 @@ class DialogCommunity extends LitElement { href="https://newsletter.openhomefoundation.org/" > - + Open Home Foundation Logo ${this.localize( "ui.panel.page-onboarding.welcome.open_home_newsletter" )} @@ -64,7 +72,11 @@ class DialogCommunity extends LitElement { href="https://www.home-assistant.io/join-chat" > - + Discord Logo ${this.localize("ui.panel.page-onboarding.welcome.discord")} @@ -72,11 +84,15 @@ class DialogCommunity extends LitElement { - - ${this.localize("ui.panel.page-onboarding.welcome.x")} + Mastodon Logo + ${this.localize("ui.panel.page-onboarding.welcome.mastodon")} @@ -96,12 +112,6 @@ class DialogCommunity extends LitElement { a { text-decoration: none; } - - @media (prefers-color-scheme: light) { - img.x { - filter: invert(1) hue-rotate(180deg); - } - } `; } diff --git a/src/translations/en.json b/src/translations/en.json index ade0eb7ab233..e93ed8c71261 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7355,7 +7355,9 @@ "scroll_down_button": "New logs - Click to scroll", "fetch_error": "Failed to fetch logs", "retry": "Retry" - } + }, + "error_title": "Error installing Home Assistant", + "error_description": "An error occurred while installing Home Assistant. Please check the logs for more information." }, "welcome": { "header": "Welcome!", @@ -7368,6 +7370,7 @@ "open_home_newsletter": "Building the Open Home newsletter", "discord": "Discord chat", "x": "[%key:ui::panel::config::tips::join_x%]", + "mastodon": "Mastodon", "playstore": "Get it on Google Play", "appstore": "Download on the App Store" }, From f221ac0802eb494d02e012a0b5d20c71921301f0 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 29 Oct 2024 09:22:42 +0100 Subject: [PATCH 07/33] Add es5 build for landing-page --- build-scripts/gulp/webpack.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index ff6feed8ed12..b18d3134944a 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -206,7 +206,9 @@ gulp.task("webpack-prod-gallery", () => gulp.task("webpack-dev-server-landing-page", () => runDevServer({ compiler: webpack( - createLandingPageConfig({ isProdBuild: false, latestBuild: true }) + process.env.ES5 + ? bothBuilds(createLandingPageConfig, { isProdBuild: false }) + : createLandingPageConfig({ isProdBuild: false, latestBuild: true }) ), contentBase: paths.landingPage_output_root, port: 8110, @@ -226,9 +228,10 @@ gulp.task("webpack-dev-server-landing-page", () => gulp.task("webpack-prod-landing-page", () => prodBuild( - createLandingPageConfig({ + bothBuilds(createLandingPageConfig, { isProdBuild: true, - latestBuild: true, + isStatsBuild: env.isStatsBuild(), + isTestBuild: env.isTestBuild(), }) ) ); From 31905b7a562fe4946c38a3d90a011ff0837333f9 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 30 Oct 2024 07:46:04 +0100 Subject: [PATCH 08/33] Add download_full_logs at landing-page --- build-scripts/gulp/webpack.js | 4 +- landing-page/README.md | 35 +++++++++++++ .../src/components/landing-page-logs.ts | 50 ++++++++++++++++--- landing-page/src/data/supervisor.ts | 6 ++- src/translations/en.json | 3 +- 5 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 landing-page/README.md diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index b18d3134944a..73562564dbc8 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -216,11 +216,11 @@ gulp.task("webpack-dev-server-landing-page", () => proxy: [ { context: ["/observer"], - target: "http://localhost:3000", + target: "http://localhost:8830", }, { context: ["/supervisor"], - target: "http://localhost:3000", + target: "http://localhost:8830", }, ], }) diff --git a/landing-page/README.md b/landing-page/README.md new file mode 100644 index 000000000000..6cff998f31e3 --- /dev/null +++ b/landing-page/README.md @@ -0,0 +1,35 @@ +# Home Assistant OS Landingpage + +On initial startup of Home Assistant, the HAOS needs to download Home Assistant core before the setup can start. +In this time the [home-assistant/landingpage](https://github.com/home-assistant/landingpage) is serving a "Preparing Home Assistant" page. + +## Functionality + +- Progress bar to show download +- Show / hide supervisor logs +- Links + - Read our Vision + - Join our community + - Download our app +- DNS issue handler + - if the supervisor is not able to connect to the internet + - Show actions to set dns to google or cloudflare to resolve the issue +- Error handler + - if something with the installation goes wrong, we show the logs + +## Develop + +### landingpage dev server + +- clone [home-assistant/landingpage](https://github.com/home-assistant/landingpage) +- use the dev container +- start the dev server with following optional env vars: + - `SUPERVISOR_HOST` to have real supervisor data, you can [setup a supervisor remote API access](https://developers.home-assistant.io/docs/supervisor/development/#supervisor-api-access) and set the host of your supervisor. e.g.: `SUPERVISOR_HOST=192.168.0.20:8888` + - `SUPERVISOR_TOKEN` the supervisor api token you get from the Remote API proxy Addon Logs + - example: `SUPERVISOR_TOKEN=abc123 SUPERVISOR_HOST=192.168.0.20:8888 go run main.go http.go mdns.go` + +### frontend dev server + +- install all dependencies +- run `landing-page/script/develop` + - The dev server has a proxy activated to landingpage dev server (`http://localhost:8830`) diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index a95f23e8cd1a..24aebbcc1f3a 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -1,5 +1,5 @@ import "@material/mwc-linear-progress/mwc-linear-progress"; -import { mdiArrowCollapseDown } from "@mdi/js"; +import { mdiArrowCollapseDown, mdiDownload } from "@mdi/js"; // eslint-disable-next-line import/extensions import { IntersectionController } from "@lit-labs/observers/intersection-controller.js"; import { LitElement, PropertyValues, css, html, nothing } from "lit"; @@ -7,12 +7,14 @@ import { classMap } from "lit/directives/class-map"; import { customElement, property, query, state } from "lit/decorators"; import type { LocalizeFunc } from "../../../src/common/translations/localize"; import "../../../src/components/ha-button"; +import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-ansi-to-html"; import "../../../src/components/ha-alert"; import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; import { getObserverLogs, getSupervisorLogsFollow } from "../data/supervisor"; import { fireEvent } from "../../../src/common/dom/fire_event"; +import { fileDownload } from "../../../src/util/file_download"; const ERROR_CHECK = /^[\d -:]+(ERROR|CRITICAL)(.*)/gm; declare global { @@ -49,13 +51,24 @@ class LandingPageLogs extends LitElement { protected render() { return html` - - ${this.localize( - this._show - ? "ui.panel.page-onboarding.prepare.hide_details" - : "ui.panel.page-onboarding.prepare.show_details" - )} - +
+ + ${this.localize( + this._show + ? "ui.panel.page-onboarding.prepare.hide_details" + : "ui.panel.page-onboarding.prepare.show_details" + )} + + ${this._show + ? html`` + : nothing} +
${this._error ? html` Date: Wed, 30 Oct 2024 14:32:34 +0100 Subject: [PATCH 09/33] Change dev build for landing-page --- build-scripts/gulp/landing-page.js | 9 ++--- build-scripts/gulp/webpack.js | 34 +++++++------------ landing-page/README.md | 13 +++++-- .../src/components/landing-page-logs.ts | 2 +- .../src/components/landing-page-network.ts | 4 +-- 5 files changed, 28 insertions(+), 34 deletions(-) diff --git a/build-scripts/gulp/landing-page.js b/build-scripts/gulp/landing-page.js index b54cade42ad6..b958738acad7 100644 --- a/build-scripts/gulp/landing-page.js +++ b/build-scripts/gulp/landing-page.js @@ -1,5 +1,4 @@ import gulp from "gulp"; -import env from "../env.cjs"; import "./clean.js"; import "./entry-html.js"; import "./gather-static.js"; @@ -20,11 +19,7 @@ gulp.task( gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), "copy-static-landing-page", "gen-pages-landing-page-dev", - gulp.parallel( - env.useRollup() - ? "rollup-dev-server-landing-page" - : "webpack-dev-server-landing-page" - ) + gulp.parallel("webpack-watch-landing-page") ) ); @@ -38,7 +33,7 @@ gulp.task( "translations-enable-merge-backend", gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), "copy-static-landing-page", - env.useRollup() ? "rollup-prod-landing-page" : "webpack-prod-landing-page", + "webpack-prod-landing-page", "gen-pages-landing-page-prod" ) ); diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index 73562564dbc8..51a6837d6a09 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -203,28 +203,18 @@ gulp.task("webpack-prod-gallery", () => ) ); -gulp.task("webpack-dev-server-landing-page", () => - runDevServer({ - compiler: webpack( - process.env.ES5 - ? bothBuilds(createLandingPageConfig, { isProdBuild: false }) - : createLandingPageConfig({ isProdBuild: false, latestBuild: true }) - ), - contentBase: paths.landingPage_output_root, - port: 8110, - listenHost: "0.0.0.0", - proxy: [ - { - context: ["/observer"], - target: "http://localhost:8830", - }, - { - context: ["/supervisor"], - target: "http://localhost:8830", - }, - ], - }) -); +gulp.task("webpack-watch-landing-page", () => { + // This command will run forever because we don't close compiler + webpack( + process.env.ES5 + ? bothBuilds(createLandingPageConfig, { isProdBuild: false }) + : createLandingPageConfig({ isProdBuild: false, latestBuild: true }) + ).watch({ poll: isWsl }, doneHandler()); + gulp.watch( + path.join(paths.translations_src, "en.json"), + gulp.series("build-translations", "copy-translations-app") + ); +}); gulp.task("webpack-prod-landing-page", () => prodBuild( diff --git a/landing-page/README.md b/landing-page/README.md index 6cff998f31e3..f286b886253b 100644 --- a/landing-page/README.md +++ b/landing-page/README.md @@ -19,17 +19,26 @@ In this time the [home-assistant/landingpage](https://github.com/home-assistant/ ## Develop +It is similar to the core frontend dev. + +- frontend repo is building stuff +- landingpage repo can set the frontend repo path and serve the dev frontend + ### landingpage dev server - clone [home-assistant/landingpage](https://github.com/home-assistant/landingpage) +- Add frontend repo as mount to your devcontainer config + - please do not commit this changes, you can remove it after initial dev container build, because the build will keep the options as long as you don't rebuild it. + - `"mounts": ["source=/path/to/hass/frontend,target=/workspaces/frontend,type=bind,consistency=cached"]` - use the dev container - start the dev server with following optional env vars: - `SUPERVISOR_HOST` to have real supervisor data, you can [setup a supervisor remote API access](https://developers.home-assistant.io/docs/supervisor/development/#supervisor-api-access) and set the host of your supervisor. e.g.: `SUPERVISOR_HOST=192.168.0.20:8888` - `SUPERVISOR_TOKEN` the supervisor api token you get from the Remote API proxy Addon Logs - - example: `SUPERVISOR_TOKEN=abc123 SUPERVISOR_HOST=192.168.0.20:8888 go run main.go http.go mdns.go` + - `FRONTEND_PATH` the path inside your container should be `/workspaces/frontend` + - example: `SUPERVISOR_TOKEN=abc123 SUPERVISOR_HOST=192.168.0.20:8888 FRONTEND_PATH=/workspaces/frontend go run main.go http.go mdns.go` + - You can also add this into your devcontainer settings, but then it's not so flexible to change if you want to test something else. ### frontend dev server - install all dependencies - run `landing-page/script/develop` - - The dev server has a proxy activated to landingpage dev server (`http://localhost:8830`) diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index 24aebbcc1f3a..ebf1e6ea8c6d 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -2,7 +2,7 @@ import "@material/mwc-linear-progress/mwc-linear-progress"; import { mdiArrowCollapseDown, mdiDownload } from "@mdi/js"; // eslint-disable-next-line import/extensions import { IntersectionController } from "@lit-labs/observers/intersection-controller.js"; -import { LitElement, PropertyValues, css, html, nothing } from "lit"; +import { LitElement, type PropertyValues, css, html, nothing } from "lit"; import { classMap } from "lit/directives/class-map"; import { customElement, property, query, state } from "lit/decorators"; import type { LocalizeFunc } from "../../../src/common/translations/localize"; diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts index 1fd959e2788f..52fd48c4a66c 100644 --- a/landing-page/src/components/landing-page-network.ts +++ b/landing-page/src/components/landing-page-network.ts @@ -1,8 +1,8 @@ import "@material/mwc-linear-progress/mwc-linear-progress"; import { - CSSResultGroup, + type CSSResultGroup, LitElement, - PropertyValues, + type PropertyValues, css, html, nothing, From 10997c758bd1398dc1fbf57ddf5ee67673d73def Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 4 Nov 2024 15:15:01 +0100 Subject: [PATCH 10/33] Fix landing-page build, do it like hassio --- build-scripts/bundle.cjs | 3 - build-scripts/gulp/compress.js | 16 ++++ build-scripts/gulp/entry-html.js | 3 +- build-scripts/gulp/gather-static.js | 21 ++---- build-scripts/gulp/landing-page.js | 18 +++-- build-scripts/gulp/translations.js | 18 ++++- build-scripts/gulp/webpack.js | 6 +- build-scripts/paths.cjs | 4 + landing-page/README.md | 2 +- .../src/components/landing-page-logs.ts | 28 +++---- .../src/components/landing-page-network.ts | 41 ++++------ landing-page/src/ha-landing-page.ts | 44 ++++------- landing-page/src/landing-page-base-element.ts | 74 +++++++++++++++++++ src/common/translations/localize.ts | 4 + src/translations/en.json | 64 ++++++++++------ 15 files changed, 218 insertions(+), 128 deletions(-) create mode 100644 landing-page/src/landing-page-base-element.ts diff --git a/build-scripts/bundle.cjs b/build-scripts/bundle.cjs index 5e394ae3ceba..d105a512e6e9 100644 --- a/build-scripts/bundle.cjs +++ b/build-scripts/bundle.cjs @@ -338,9 +338,6 @@ module.exports.config = { publicPath: publicPath(latestBuild), isProdBuild, latestBuild, - defineOverlay: { - __DEMO__: true, - }, }; }, }; diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js index fa171c9154b5..6a6f931b60cf 100644 --- a/build-scripts/gulp/compress.js +++ b/build-scripts/gulp/compress.js @@ -51,6 +51,12 @@ const compressHassioBrotli = () => paths.hassio_output_latest, false ); +const compressLandingPageBrotli = () => + compressDistBrotli( + paths.landingPage_output_root, + paths.landingPage_output_latest, + false + ); const compressAppZopfli = () => compressDistZopfli(paths.app_output_root, paths.app_output_latest); @@ -60,9 +66,19 @@ const compressHassioZopfli = () => paths.hassio_output_latest, true ); +const compressLandingPageZopfli = () => + compressDistZopfli( + paths.landingPage_output_root, + paths.landingPage_output_latest, + true + ); gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli)); gulp.task( "compress-hassio", gulp.parallel(compressHassioBrotli, compressHassioZopfli) ); +gulp.task( + "compress-landing-page", + gulp.parallel(compressLandingPageBrotli, compressLandingPageZopfli) +); diff --git a/build-scripts/gulp/entry-html.js b/build-scripts/gulp/entry-html.js index 64f151e48f9b..ec7da3c1c025 100644 --- a/build-scripts/gulp/entry-html.js +++ b/build-scripts/gulp/entry-html.js @@ -274,7 +274,8 @@ gulp.task( LANDING_PAGE_PAGE_ENTRIES, paths.landingPage_dir, paths.landingPage_output_root, - paths.landingPage_output_latest + paths.landingPage_output_latest, + paths.landingPage_output_es5 ) ); diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index 2899e78b001c..48b90cf02974 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -27,16 +27,6 @@ function copyTranslations(staticDir) { ); } -function copyFragmentTranslations(staticDir, pageName) { - const staticPath = genStaticPath(staticDir); - - // Translation output - fs.copySync( - polyPath(`build/translations/output/${pageName}`), - staticPath(`translations/${pageName}`) - ); -} - function copyLocaleData(staticDir) { const staticPath = genStaticPath(staticDir); @@ -135,6 +125,11 @@ gulp.task("copy-translations-supervisor", async () => { copyTranslations(staticDir); }); +gulp.task("copy-translations-landing-page", async () => { + const staticDir = paths.landingPage_output_static; + copyTranslations(staticDir); +}); + gulp.task("copy-static-supervisor", async () => { const staticDir = paths.hassio_output_static; copyLocaleData(staticDir); @@ -211,14 +206,12 @@ gulp.task("copy-static-gallery", async () => { }); gulp.task("copy-static-landing-page", async () => { - // Copy app static files - fs.copySync(polyPath("public/static"), paths.gallery_output_static); - // Copy gallery static files + // Copy landing-page static files fs.copySync( path.resolve(paths.landingPage_dir, "public"), paths.landingPage_output_root ); copyFonts(paths.landingPage_output_static); - copyFragmentTranslations(paths.landingPage_output_static, "page-onboarding"); + copyTranslations(paths.landingPage_output_static); }); diff --git a/build-scripts/gulp/landing-page.js b/build-scripts/gulp/landing-page.js index b958738acad7..f193bc885bd7 100644 --- a/build-scripts/gulp/landing-page.js +++ b/build-scripts/gulp/landing-page.js @@ -1,10 +1,10 @@ import gulp from "gulp"; +import env from "../env.cjs"; import "./clean.js"; +import "./compress.js"; import "./entry-html.js"; import "./gather-static.js"; import "./gen-icons-json.js"; -import "./rollup.js"; -import "./service-worker.js"; import "./translations.js"; import "./webpack.js"; @@ -16,10 +16,12 @@ gulp.task( }, "clean-landing-page", "translations-enable-merge-backend", - gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), + "build-landing-page-translations", + "copy-translations-landing-page", + "build-locale-data", "copy-static-landing-page", "gen-pages-landing-page-dev", - gulp.parallel("webpack-watch-landing-page") + "webpack-watch-landing-page" ) ); @@ -30,10 +32,12 @@ gulp.task( process.env.NODE_ENV = "production"; }, "clean-landing-page", - "translations-enable-merge-backend", - gulp.parallel("gen-icons-json", "build-translations", "build-locale-data"), + "build-landing-page-translations", + "copy-translations-landing-page", + "build-locale-data", "copy-static-landing-page", "webpack-prod-landing-page", - "gen-pages-landing-page-prod" + "gen-pages-landing-page-prod", + ...(env.isTestBuild() ? [] : ["compress-landing-page"]) // Don't compress running tests ) ); diff --git a/build-scripts/gulp/translations.js b/build-scripts/gulp/translations.js index 1127a5d3f5eb..059babc09215 100755 --- a/build-scripts/gulp/translations.js +++ b/build-scripts/gulp/translations.js @@ -172,12 +172,14 @@ const createMasterTranslation = () => const FRAGMENTS = ["base"]; -const toggleSupervisorFragment = async () => { - FRAGMENTS[0] = "supervisor"; +const setFragment = (fragment) => async () => { + FRAGMENTS[0] = fragment; }; const panelFragment = (fragment) => - fragment !== "base" && fragment !== "supervisor"; + fragment !== "base" && + fragment !== "supervisor" && + fragment !== "landing-page"; const HASHES = new Map(); @@ -224,6 +226,9 @@ const createTranslations = async () => { case "supervisor": // Supervisor key is at the top level return [flatten(data.supervisor), ""]; + case "landing-page": + // landing-page key is at the top level + return [flatten(data["landing-page"]), ""]; default: // Create a fragment with only the given panel return [ @@ -322,5 +327,10 @@ gulp.task( gulp.task( "build-supervisor-translations", - gulp.series(toggleSupervisorFragment, "build-translations") + gulp.series(setFragment("supervisor"), "build-translations") +); + +gulp.task( + "build-landing-page-translations", + gulp.series(setFragment("landing-page"), "build-translations") ); diff --git a/build-scripts/gulp/webpack.js b/build-scripts/gulp/webpack.js index 51a6837d6a09..f46ce61bfd1a 100644 --- a/build-scripts/gulp/webpack.js +++ b/build-scripts/gulp/webpack.js @@ -210,9 +210,13 @@ gulp.task("webpack-watch-landing-page", () => { ? bothBuilds(createLandingPageConfig, { isProdBuild: false }) : createLandingPageConfig({ isProdBuild: false, latestBuild: true }) ).watch({ poll: isWsl }, doneHandler()); + gulp.watch( path.join(paths.translations_src, "en.json"), - gulp.series("build-translations", "copy-translations-app") + gulp.series( + "build-landing-page-translations", + "copy-translations-landing-page" + ) ); }); diff --git a/build-scripts/paths.cjs b/build-scripts/paths.cjs index adc790a25eb4..fc8357d94789 100644 --- a/build-scripts/paths.cjs +++ b/build-scripts/paths.cjs @@ -40,6 +40,10 @@ module.exports = { __dirname, "../landing-page/dist/frontend_latest" ), + landingPage_output_es5: path.resolve( + __dirname, + "../landing-page/dist/frontend_es5" + ), landingPage_output_static: path.resolve( __dirname, "../landing-page/dist/static" diff --git a/landing-page/README.md b/landing-page/README.md index f286b886253b..005bfa3d8318 100644 --- a/landing-page/README.md +++ b/landing-page/README.md @@ -1,6 +1,6 @@ # Home Assistant OS Landingpage -On initial startup of Home Assistant, the HAOS needs to download Home Assistant core before the setup can start. +On initial startup of Home Assistant, HAOS needs to download Home Assistant core before the setup can start. In this time the [home-assistant/landingpage](https://github.com/home-assistant/landingpage) is serving a "Preparing Home Assistant" page. ## Functionality diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index ebf1e6ea8c6d..f54347c64e0f 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -5,7 +5,10 @@ import { IntersectionController } from "@lit-labs/observers/intersection-control import { LitElement, type PropertyValues, css, html, nothing } from "lit"; import { classMap } from "lit/directives/class-map"; import { customElement, property, query, state } from "lit/decorators"; -import type { LocalizeFunc } from "../../../src/common/translations/localize"; +import type { + LandingPageKeys, + LocalizeFunc, +} from "../../../src/common/translations/localize"; import "../../../src/components/ha-button"; import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-svg-icon"; @@ -25,7 +28,8 @@ declare global { @customElement("landing-page-logs") class LandingPageLogs extends LitElement { - @property({ attribute: false }) public localize!: LocalizeFunc; + @property({ attribute: false }) + public localize!: LocalizeFunc; @query("ha-ansi-to-html") private _ansiToHtmlElement?: HaAnsiToHtml; @@ -53,17 +57,11 @@ class LandingPageLogs extends LitElement { return html`
- ${this.localize( - this._show - ? "ui.panel.page-onboarding.prepare.hide_details" - : "ui.panel.page-onboarding.prepare.show_details" - )} + ${this.localize(this._show ? "hide_details" : "show_details")} ${this._show ? html`` @@ -73,12 +71,10 @@ class LandingPageLogs extends LitElement { ? html` - ${this.localize("ui.panel.page-onboarding.prepare.logs.retry")} + ${this.localize("logs.retry")} ` @@ -103,9 +99,7 @@ class LandingPageLogs extends LitElement { @click=${this._scrollToBottom} > - ${this.localize( - "ui.panel.page-onboarding.prepare.logs.scroll_down_button" - )} + ${this.localize("logs.scroll_down_button")} ; @state() private _networkIssue = false; @@ -55,11 +55,7 @@ class LandingPageNetwork extends LitElement { if (this._getNetworkInfoError) { return html` -

- ${this.localize( - "ui.panel.page-onboarding.prepare.network_issue.error_get_network_info" - )} -

+

${this.localize("network_issue.error_get_network_info")}

`; } @@ -67,29 +63,18 @@ class LandingPageNetwork extends LitElement { return html`

- ${this.localize( - "ui.panel.page-onboarding.prepare.network_issue.description", - { dns: this._dnsPrimaryInterface || "?" } - )} -

-

- ${this.localize( - "ui.panel.page-onboarding.prepare.network_issue.resolve_different" - )} + ${this.localize("network_issue.description", { + dns: this._dnsPrimaryInterface || "?", + })}

+

${this.localize("network_issue.resolve_different")}

${!this._dnsPrimaryInterface ? html`

- ${this.localize( - "ui.panel.page-onboarding.prepare.network_issue.no_primary_interface" - )} - + ${this.localize("network_issue.no_primary_interface")}

` : nothing} diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index 5a30a556655e..34ac09f43440 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -1,30 +1,21 @@ import "@material/mwc-linear-progress"; -import { LitElement, type PropertyValues, css, html, nothing } from "lit"; +import { type PropertyValues, css, html, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import "../../src/components/ha-alert"; import { haStyle } from "../../src/resources/styles"; import "../../src/onboarding/onboarding-welcome-links"; import "./components/landing-page-network"; import "./components/landing-page-logs"; -import { litLocalizeLiteMixin } from "../../src/mixins/lit-localize-lite-mixin"; import { extractSearchParam } from "../../src/common/url/search-params"; import { onBoardingStyles } from "../../src/onboarding/styles"; -import type { Constructor } from "../../src/types"; -import themesMixin from "../../src/state/themes-mixin"; import { makeDialogManager } from "../../src/dialogs/make-dialog-manager"; -import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin"; +import { LandingPageBaseElement } from "./landing-page-base-element"; const SCHEDULE_CORE_CHECK_SECONDS = 5; -const ext = (baseClass: T, mixins): T => - mixins.reduceRight((base, mixin) => mixin(base), baseClass); - @customElement("ha-landing-page") -class HaLandingPage extends litLocalizeLiteMixin( - ext(ProvideHassLitMixin(LitElement), [themesMixin]) -) { - @property({ attribute: false }) public translationFragment = - "page-onboarding"; +class HaLandingPage extends LandingPageBaseElement { + @property({ attribute: false }) public translationFragment = "landing-page"; @state() private _networkIssue = false; @@ -37,12 +28,10 @@ class HaLandingPage extends litLocalizeLiteMixin( return html`
-

${this.localize("ui.panel.page-onboarding.prepare.header")}

+

${this.localize("header")}

${!this._networkIssue && !this._supervisorError ? html` -

- ${this.localize("ui.panel.page-onboarding.prepare.subheader")} -

+

${this.localize("subheader")}

` : nothing} @@ -55,13 +44,9 @@ class HaLandingPage extends litLocalizeLiteMixin( ? html` - ${this.localize( - "ui.panel.page-onboarding.prepare.error_description" - )} + ${this.localize("error_description")} ` : nothing} @@ -133,12 +118,13 @@ class HaLandingPage extends litLocalizeLiteMixin( private _languageChanged(ev: CustomEvent) { const language = ev.detail.value; - this.language = language; - try { - localStorage.setItem("selectedLanguage", JSON.stringify(language)); - location.reload(); - } catch (err: any) { - // Ignore + if (language !== this.language && language) { + this.language = language; + try { + localStorage.setItem("selectedLanguage", JSON.stringify(language)); + } catch (err: any) { + // Ignore + } } } diff --git a/landing-page/src/landing-page-base-element.ts b/landing-page/src/landing-page-base-element.ts new file mode 100644 index 000000000000..9c7a544ddfc9 --- /dev/null +++ b/landing-page/src/landing-page-base-element.ts @@ -0,0 +1,74 @@ +import type { PropertyValues } from "lit"; +import { LitElement } from "lit"; +import { property, state } from "lit/decorators"; +import { + computeLocalize, + type LandingPageKeys, + type LocalizeFunc, +} from "../../src/common/translations/localize"; +import { ProvideHassLitMixin } from "../../src/mixins/provide-hass-lit-mixin"; +import type { Constructor, Resources } from "../../src/types"; +import { + getLocalLanguage, + getTranslation, +} from "../../src/util/common-translation"; +import { computeDirectionStyles } from "../../src/common/util/compute_rtl"; +import themesMixin from "../../src/state/themes-mixin"; +import { translationMetadata } from "../../src/resources/translations-metadata"; +import type { HassBaseEl } from "../../src/state/hass-base-mixin"; + +export class LandingPageBaseElement extends themesMixin( + ProvideHassLitMixin(LitElement) as unknown as Constructor +) { + // Initialized to empty will prevent undefined errors if called before connected to DOM. + @property({ attribute: false }) + public localize: LocalizeFunc = () => ""; + + // Use browser language setup before login. + @property() public language?: string = getLocalLanguage(); + + @state() private _resources?: Resources; + + public connectedCallback(): void { + super.connectedCallback(); + this._initializeLocalize(); + } + + protected willUpdate(changedProperties: PropertyValues) { + if (changedProperties.get("language")) { + this._resources = undefined; + this._initializeLocalize(); + } + + if ( + this.language && + this._resources && + (changedProperties.has("language") || changedProperties.has("_resources")) + ) { + this._setLocalize(); + } + } + + private async _initializeLocalize() { + if (this._resources || !this.language) { + return; + } + + const { data } = await getTranslation(null, this.language); + this._resources = { + [this.language]: data, + }; + } + + private async _setLocalize() { + this.localize = await computeLocalize( + this.constructor.prototype, + this.language!, + this._resources! + ); + computeDirectionStyles( + translationMetadata.translations[this.language!].isRTL, + this + ); + } +} diff --git a/src/common/translations/localize.ts b/src/common/translations/localize.ts index 66da3c4327c9..32323b4b2573 100644 --- a/src/common/translations/localize.ts +++ b/src/common/translations/localize.ts @@ -33,6 +33,10 @@ export type LocalizeKeys = | `ui.panel.page-authorize.form.${string}` | `component.${string}`; +export type LandingPageKeys = FlattenObjectKeys< + TranslationDict["landing-page"] +>; + // Tweaked from https://www.raygesualdo.com/posts/flattening-object-keys-with-typescript-types export type FlattenObjectKeys< T extends Record, diff --git a/src/translations/en.json b/src/translations/en.json index 488e6f5f43f2..961441ba929b 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7337,29 +7337,6 @@ "next": "Next", "finish": "Finish", "help": "Help", - "prepare": { - "header": "Preparing Home Assistant", - "subheader": "This may take 20 minutes or more", - "show_details": "Show details", - "hide_details": "Hide details", - "network_issue": { - "title": "Networking issue detected", - "error_get_network_info": "Cannot get network information", - "description": "Home Assistant OS detected a networking issue in your setup. As part of the initial setup, Home Assistant OS downloads the latest version of Home Assistant Core. This networking issue prevents this download. The network issue might be DNS related. The currently used DNS service is: {dns}.", - "resolve_different": "To resolve this, you can try a different DNS server. Select one of the options below. Alternatively, change your router configuration to use your own custom DNS server.", - "use_cloudflare": "Use Cloudflare DNS", - "use_google": "Use Google DNS", - "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!" - }, - "logs": { - "scroll_down_button": "New logs - Click to scroll", - "fetch_error": "Failed to fetch logs", - "retry": "Retry", - "download_full_log": "[%key:ui::panel::config::logs::download_full_log%]" - }, - "error_title": "Error installing Home Assistant", - "error_description": "An error occurred while installing Home Assistant. Please check the logs for more information." - }, "welcome": { "header": "Welcome!", "start": "Create my smart home", @@ -7520,6 +7497,47 @@ "key_m_hint": "Press 'm' on any page to get the My Home Assistant link" } }, + "landing-page": { + "header": "Preparing Home Assistant", + "subheader": "This may take 20 minutes or more", + "show_details": "Show details", + "hide_details": "Hide details", + "network_issue": { + "title": "Networking issue detected", + "error_get_network_info": "Cannot get network information", + "description": "Home Assistant OS detected a networking issue in your setup. As part of the initial setup, Home Assistant OS downloads the latest version of Home Assistant Core. This networking issue prevents this download. The network issue might be DNS related. The currently used DNS service is: {dns}.", + "resolve_different": "To resolve this, you can try a different DNS server. Select one of the options below. Alternatively, change your router configuration to use your own custom DNS server.", + "use_cloudflare": "Use Cloudflare DNS", + "use_google": "Use Google DNS", + "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!" + }, + "logs": { + "scroll_down_button": "New logs - Click to scroll", + "fetch_error": "Failed to fetch logs", + "retry": "Retry", + "download_full_log": "[%key:ui::panel::config::logs::download_full_log%]" + }, + "error_title": "Error installing Home Assistant", + "error_description": "An error occurred while installing Home Assistant. Please check the logs for more information.", + "ui": { + "panel": { + "page-onboarding": { + "welcome": { + "vision": "[%key:ui::panel::page-onboarding::welcome::vision%]", + "community": "[%key:ui::panel::page-onboarding::welcome::community%]", + "download_app": "[%key:ui::panel::page-onboarding::welcome::download_app%]", + "forums": "[%key:ui::panel::page-onboarding::welcome::forums%]", + "open_home_newsletter": "[%key:ui::panel::page-onboarding::welcome::open_home_newsletter%]", + "discord": "[%key:ui::panel::page-onboarding::welcome::discord%]", + "mastodon": "[%key:ui::panel::page-onboarding::welcome::mastodon%]", + "playstore": "[%key:ui::panel::page-onboarding::welcome::playstore%]", + "appstore": "[%key:ui::panel::page-onboarding::welcome::appstore%]" + }, + "help": "[%key:ui::panel::page-onboarding::help%]" + } + } + } + }, "supervisor": { "addon": { "failed_to_reset": "Failed to reset add-on configuration, {error}", From 67cd512bb4745d4e239e7096673dad73650f2b9d Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 4 Nov 2024 15:55:06 +0100 Subject: [PATCH 11/33] Remove compress from landing-page build --- build-scripts/gulp/compress.js | 16 ---------------- build-scripts/gulp/landing-page.js | 4 +--- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js index 6a6f931b60cf..fa171c9154b5 100644 --- a/build-scripts/gulp/compress.js +++ b/build-scripts/gulp/compress.js @@ -51,12 +51,6 @@ const compressHassioBrotli = () => paths.hassio_output_latest, false ); -const compressLandingPageBrotli = () => - compressDistBrotli( - paths.landingPage_output_root, - paths.landingPage_output_latest, - false - ); const compressAppZopfli = () => compressDistZopfli(paths.app_output_root, paths.app_output_latest); @@ -66,19 +60,9 @@ const compressHassioZopfli = () => paths.hassio_output_latest, true ); -const compressLandingPageZopfli = () => - compressDistZopfli( - paths.landingPage_output_root, - paths.landingPage_output_latest, - true - ); gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli)); gulp.task( "compress-hassio", gulp.parallel(compressHassioBrotli, compressHassioZopfli) ); -gulp.task( - "compress-landing-page", - gulp.parallel(compressLandingPageBrotli, compressLandingPageZopfli) -); diff --git a/build-scripts/gulp/landing-page.js b/build-scripts/gulp/landing-page.js index f193bc885bd7..3a8f38749354 100644 --- a/build-scripts/gulp/landing-page.js +++ b/build-scripts/gulp/landing-page.js @@ -1,5 +1,4 @@ import gulp from "gulp"; -import env from "../env.cjs"; import "./clean.js"; import "./compress.js"; import "./entry-html.js"; @@ -37,7 +36,6 @@ gulp.task( "build-locale-data", "copy-static-landing-page", "webpack-prod-landing-page", - "gen-pages-landing-page-prod", - ...(env.isTestBuild() ? [] : ["compress-landing-page"]) // Don't compress running tests + "gen-pages-landing-page-prod" ) ); From 777653425370170de3a120d642554d7fc202c2d9 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 19 Nov 2024 11:14:07 +0100 Subject: [PATCH 12/33] Migrate landing-page eslint config --- landing-page/.eslintrc.json | 3 --- landing-page/eslintrc.config.mjs | 8 ++++++++ 2 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 landing-page/.eslintrc.json create mode 100644 landing-page/eslintrc.config.mjs diff --git a/landing-page/.eslintrc.json b/landing-page/.eslintrc.json deleted file mode 100644 index 659ccca5615d..000000000000 --- a/landing-page/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../.eslintrc.json" -} diff --git a/landing-page/eslintrc.config.mjs b/landing-page/eslintrc.config.mjs new file mode 100644 index 000000000000..faf25c873f8e --- /dev/null +++ b/landing-page/eslintrc.config.mjs @@ -0,0 +1,8 @@ +import rootConfig from "../eslint.config.mjs"; + +export default [ + ...rootConfig, + { + rules: {}, + }, +]; From 88457d16a5461975f3d7d432c0da656cec01efc1 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 21 Oct 2024 12:56:25 +0200 Subject: [PATCH 13/33] Add landing-page project --- landing-page/.eslintrc.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 landing-page/.eslintrc.json diff --git a/landing-page/.eslintrc.json b/landing-page/.eslintrc.json new file mode 100644 index 000000000000..59c30e3b6d13 --- /dev/null +++ b/landing-page/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "../.eslintrc.json", + "rules": { + "no-console": 0 + } +} From 0963ec13cb9b242418a03b1f4e0bff9da5faf600 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 21 Oct 2024 14:14:29 +0200 Subject: [PATCH 14/33] Add landing-page-prepare --- .../src/components/landing-page-prepare.ts | 59 +++++++++++++++++++ src/translations/en.json | 6 ++ 2 files changed, 65 insertions(+) create mode 100644 landing-page/src/components/landing-page-prepare.ts diff --git a/landing-page/src/components/landing-page-prepare.ts b/landing-page/src/components/landing-page-prepare.ts new file mode 100644 index 000000000000..1eeea67d177f --- /dev/null +++ b/landing-page/src/components/landing-page-prepare.ts @@ -0,0 +1,59 @@ +import "@material/mwc-linear-progress/mwc-linear-progress"; +import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import type { LocalizeFunc } from "../../../src/common/translations/localize"; +import type { HomeAssistant } from "../../../src/types"; +import { onBoardingStyles } from "../../../src/onboarding/styles"; +import "../../../src/components/ha-button"; + +@customElement("landing-page-prepare") +class LandingPagePrepare extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ attribute: false }) public localize!: LocalizeFunc; + + @state() private _showDetails = false; + + protected render(): TemplateResult { + return html` +

${this.localize("ui.panel.page-onboarding.prepare.header")}

+

${this.localize("ui.panel.page-onboarding.prepare.subheader")}

+ + + + ${this.localize( + this._showDetails + ? "ui.panel.page-onboarding.prepare.hide_details" + : "ui.panel.page-onboarding.prepare.show_details" + )} + + `; + } + + private _toggleDetails(): void { + this._showDetails = !this._showDetails; + } + + static get styles(): CSSResultGroup { + return [ + onBoardingStyles, + css` + :host { + display: flex; + flex-direction: column; + align-items: center; + } + mwc-linear-progress { + width: 100%; + margin: 32px 0; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "landing-page-prepare": LandingPagePrepare; + } +} diff --git a/src/translations/en.json b/src/translations/en.json index 961441ba929b..39d18bf7094d 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7337,6 +7337,12 @@ "next": "Next", "finish": "Finish", "help": "Help", + "prepare": { + "header": "Preparing Home Assistant", + "subheader": "This may take 20 minutes or more", + "show_details": "Show details", + "hide_details": "Hide details" + }, "welcome": { "header": "Welcome!", "start": "Create my smart home", From 487bc8ea6f66dfbe6807112f3800722ed78221bf Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 22 Oct 2024 15:24:05 +0200 Subject: [PATCH 15/33] Add landing-page-network --- .../src/components/landing-page-prepare.ts | 59 ------------------- landing-page/src/ha-landing-page.ts | 6 ++ src/translations/en.json | 11 +++- 3 files changed, 16 insertions(+), 60 deletions(-) delete mode 100644 landing-page/src/components/landing-page-prepare.ts diff --git a/landing-page/src/components/landing-page-prepare.ts b/landing-page/src/components/landing-page-prepare.ts deleted file mode 100644 index 1eeea67d177f..000000000000 --- a/landing-page/src/components/landing-page-prepare.ts +++ /dev/null @@ -1,59 +0,0 @@ -import "@material/mwc-linear-progress/mwc-linear-progress"; -import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit"; -import { customElement, property, state } from "lit/decorators"; -import type { LocalizeFunc } from "../../../src/common/translations/localize"; -import type { HomeAssistant } from "../../../src/types"; -import { onBoardingStyles } from "../../../src/onboarding/styles"; -import "../../../src/components/ha-button"; - -@customElement("landing-page-prepare") -class LandingPagePrepare extends LitElement { - @property({ attribute: false }) public hass!: HomeAssistant; - - @property({ attribute: false }) public localize!: LocalizeFunc; - - @state() private _showDetails = false; - - protected render(): TemplateResult { - return html` -

${this.localize("ui.panel.page-onboarding.prepare.header")}

-

${this.localize("ui.panel.page-onboarding.prepare.subheader")}

- - - - ${this.localize( - this._showDetails - ? "ui.panel.page-onboarding.prepare.hide_details" - : "ui.panel.page-onboarding.prepare.show_details" - )} - - `; - } - - private _toggleDetails(): void { - this._showDetails = !this._showDetails; - } - - static get styles(): CSSResultGroup { - return [ - onBoardingStyles, - css` - :host { - display: flex; - flex-direction: column; - align-items: center; - } - mwc-linear-progress { - width: 100%; - margin: 32px 0; - } - `, - ]; - } -} - -declare global { - interface HTMLElementTagNameMap { - "landing-page-prepare": LandingPagePrepare; - } -} diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index 34ac09f43440..64deb60e6ac8 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -21,6 +21,8 @@ class HaLandingPage extends LandingPageBaseElement { @state() private _supervisorError = false; + @state() private _logDetails = false; + private _mobileApp = extractSearchParam("redirect_uri") === "homeassistant://auth-callback"; @@ -116,6 +118,10 @@ class HaLandingPage extends LandingPageBaseElement { this._networkIssue = ev.detail.value; } + private _toggleLogDetails() { + this._logDetails = !this._logDetails; + } + private _languageChanged(ev: CustomEvent) { const language = ev.detail.value; if (language !== this.language && language) { diff --git a/src/translations/en.json b/src/translations/en.json index 39d18bf7094d..827cce708490 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7341,7 +7341,16 @@ "header": "Preparing Home Assistant", "subheader": "This may take 20 minutes or more", "show_details": "Show details", - "hide_details": "Hide details" + "hide_details": "Hide details", + "network_issue": { + "title": "Networking issue detected", + "error_get_network_info": "Cannot get network information", + "description": "Home Assistant OS detected a networking issue in your setup. As part of the initial setup, Home Assistant OS downloads the latest version of Home Assistant Core. This networking issue prevents this download. The network issue might be DNS related. The currently used DNS service is: {dns}.", + "resolve_different": "To resolve this, you can try a different DNS server. Select one of the options below. Alternatively, change your router configuration to use your own custom DNS server.", + "use_cloudflare": "Use Cloudflare DNS", + "use_google": "Use Google DNS", + "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!" + } }, "welcome": { "header": "Welcome!", From 16a2ff31b1c0bcf5201813dede84a863ff90631d Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 23 Oct 2024 15:17:14 +0200 Subject: [PATCH 16/33] Add logs to landing-page --- landing-page/.eslintrc.json | 5 +---- landing-page/src/data/mockFetch.ts | 3 +++ landing-page/src/ha-landing-page.ts | 6 ------ src/translations/en.json | 5 +++++ 4 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 landing-page/src/data/mockFetch.ts diff --git a/landing-page/.eslintrc.json b/landing-page/.eslintrc.json index 59c30e3b6d13..659ccca5615d 100644 --- a/landing-page/.eslintrc.json +++ b/landing-page/.eslintrc.json @@ -1,6 +1,3 @@ { - "extends": "../.eslintrc.json", - "rules": { - "no-console": 0 - } + "extends": "../.eslintrc.json" } diff --git a/landing-page/src/data/mockFetch.ts b/landing-page/src/data/mockFetch.ts new file mode 100644 index 000000000000..3d99221b720f --- /dev/null +++ b/landing-page/src/data/mockFetch.ts @@ -0,0 +1,3 @@ +const mockFetch = async (url: string) => fetch(`http://localhost:3000${url}`); + +export default mockFetch; diff --git a/landing-page/src/ha-landing-page.ts b/landing-page/src/ha-landing-page.ts index 64deb60e6ac8..34ac09f43440 100644 --- a/landing-page/src/ha-landing-page.ts +++ b/landing-page/src/ha-landing-page.ts @@ -21,8 +21,6 @@ class HaLandingPage extends LandingPageBaseElement { @state() private _supervisorError = false; - @state() private _logDetails = false; - private _mobileApp = extractSearchParam("redirect_uri") === "homeassistant://auth-callback"; @@ -118,10 +116,6 @@ class HaLandingPage extends LandingPageBaseElement { this._networkIssue = ev.detail.value; } - private _toggleLogDetails() { - this._logDetails = !this._logDetails; - } - private _languageChanged(ev: CustomEvent) { const language = ev.detail.value; if (language !== this.language && language) { diff --git a/src/translations/en.json b/src/translations/en.json index 827cce708490..3a54992d897c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7350,6 +7350,11 @@ "use_cloudflare": "Use Cloudflare DNS", "use_google": "Use Google DNS", "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!" + }, + "logs": { + "scroll_down_button": "New logs - Click to scroll", + "fetch_error": "Failed to fetch logs", + "retry": "Retry" } }, "welcome": { From 6f6d18885e7ffc44b3e287657a8c77514c6ad8e2 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 23 Oct 2024 15:52:47 +0200 Subject: [PATCH 17/33] Improve landing-page build --- build-scripts/gulp/gather-static.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index 48b90cf02974..263f1980f76e 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -27,6 +27,16 @@ function copyTranslations(staticDir) { ); } +function copyFragmentTranslations(staticDir, pageName) { + const staticPath = genStaticPath(staticDir); + + // Translation output + fs.copySync( + polyPath(`build/translations/output/${pageName}`), + staticPath(`translations/${pageName}`) + ); +} + function copyLocaleData(staticDir) { const staticPath = genStaticPath(staticDir); From 7d6986bd8fbb4e20bdeb3160f1c91c223a00d0b9 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Thu, 24 Oct 2024 14:54:55 +0200 Subject: [PATCH 18/33] Improve landing-page logs and detail dialogs --- landing-page/src/data/mockFetch.ts | 3 --- src/translations/en.json | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) delete mode 100644 landing-page/src/data/mockFetch.ts diff --git a/landing-page/src/data/mockFetch.ts b/landing-page/src/data/mockFetch.ts deleted file mode 100644 index 3d99221b720f..000000000000 --- a/landing-page/src/data/mockFetch.ts +++ /dev/null @@ -1,3 +0,0 @@ -const mockFetch = async (url: string) => fetch(`http://localhost:3000${url}`); - -export default mockFetch; diff --git a/src/translations/en.json b/src/translations/en.json index 3a54992d897c..f1181a34587c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7355,7 +7355,9 @@ "scroll_down_button": "New logs - Click to scroll", "fetch_error": "Failed to fetch logs", "retry": "Retry" - } + }, + "error_title": "Error installing Home Assistant", + "error_description": "An error occurred while installing Home Assistant. Please check the logs for more information." }, "welcome": { "header": "Welcome!", From b86e7a4bbf686de5639fd81df9f013ac24b140ed Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 30 Oct 2024 07:46:04 +0100 Subject: [PATCH 19/33] Add download_full_logs at landing-page --- src/translations/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index f1181a34587c..eec2918f0fe2 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7354,7 +7354,8 @@ "logs": { "scroll_down_button": "New logs - Click to scroll", "fetch_error": "Failed to fetch logs", - "retry": "Retry" + "retry": "Retry", + "download_full_log": "[%key:ui::panel::config::logs::download_full_log%]" }, "error_title": "Error installing Home Assistant", "error_description": "An error occurred while installing Home Assistant. Please check the logs for more information." From 8081d4781434587e4728444db4699c0203befd88 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 4 Nov 2024 15:15:01 +0100 Subject: [PATCH 20/33] Fix landing-page build, do it like hassio --- build-scripts/gulp/compress.js | 16 ++++++++++++++++ build-scripts/gulp/gather-static.js | 10 ---------- build-scripts/gulp/landing-page.js | 4 +++- src/translations/en.json | 23 ----------------------- 4 files changed, 19 insertions(+), 34 deletions(-) diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js index fa171c9154b5..6a6f931b60cf 100644 --- a/build-scripts/gulp/compress.js +++ b/build-scripts/gulp/compress.js @@ -51,6 +51,12 @@ const compressHassioBrotli = () => paths.hassio_output_latest, false ); +const compressLandingPageBrotli = () => + compressDistBrotli( + paths.landingPage_output_root, + paths.landingPage_output_latest, + false + ); const compressAppZopfli = () => compressDistZopfli(paths.app_output_root, paths.app_output_latest); @@ -60,9 +66,19 @@ const compressHassioZopfli = () => paths.hassio_output_latest, true ); +const compressLandingPageZopfli = () => + compressDistZopfli( + paths.landingPage_output_root, + paths.landingPage_output_latest, + true + ); gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli)); gulp.task( "compress-hassio", gulp.parallel(compressHassioBrotli, compressHassioZopfli) ); +gulp.task( + "compress-landing-page", + gulp.parallel(compressLandingPageBrotli, compressLandingPageZopfli) +); diff --git a/build-scripts/gulp/gather-static.js b/build-scripts/gulp/gather-static.js index 263f1980f76e..48b90cf02974 100644 --- a/build-scripts/gulp/gather-static.js +++ b/build-scripts/gulp/gather-static.js @@ -27,16 +27,6 @@ function copyTranslations(staticDir) { ); } -function copyFragmentTranslations(staticDir, pageName) { - const staticPath = genStaticPath(staticDir); - - // Translation output - fs.copySync( - polyPath(`build/translations/output/${pageName}`), - staticPath(`translations/${pageName}`) - ); -} - function copyLocaleData(staticDir) { const staticPath = genStaticPath(staticDir); diff --git a/build-scripts/gulp/landing-page.js b/build-scripts/gulp/landing-page.js index 3a8f38749354..f193bc885bd7 100644 --- a/build-scripts/gulp/landing-page.js +++ b/build-scripts/gulp/landing-page.js @@ -1,4 +1,5 @@ import gulp from "gulp"; +import env from "../env.cjs"; import "./clean.js"; import "./compress.js"; import "./entry-html.js"; @@ -36,6 +37,7 @@ gulp.task( "build-locale-data", "copy-static-landing-page", "webpack-prod-landing-page", - "gen-pages-landing-page-prod" + "gen-pages-landing-page-prod", + ...(env.isTestBuild() ? [] : ["compress-landing-page"]) // Don't compress running tests ) ); diff --git a/src/translations/en.json b/src/translations/en.json index eec2918f0fe2..961441ba929b 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7337,29 +7337,6 @@ "next": "Next", "finish": "Finish", "help": "Help", - "prepare": { - "header": "Preparing Home Assistant", - "subheader": "This may take 20 minutes or more", - "show_details": "Show details", - "hide_details": "Hide details", - "network_issue": { - "title": "Networking issue detected", - "error_get_network_info": "Cannot get network information", - "description": "Home Assistant OS detected a networking issue in your setup. As part of the initial setup, Home Assistant OS downloads the latest version of Home Assistant Core. This networking issue prevents this download. The network issue might be DNS related. The currently used DNS service is: {dns}.", - "resolve_different": "To resolve this, you can try a different DNS server. Select one of the options below. Alternatively, change your router configuration to use your own custom DNS server.", - "use_cloudflare": "Use Cloudflare DNS", - "use_google": "Use Google DNS", - "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!" - }, - "logs": { - "scroll_down_button": "New logs - Click to scroll", - "fetch_error": "Failed to fetch logs", - "retry": "Retry", - "download_full_log": "[%key:ui::panel::config::logs::download_full_log%]" - }, - "error_title": "Error installing Home Assistant", - "error_description": "An error occurred while installing Home Assistant. Please check the logs for more information." - }, "welcome": { "header": "Welcome!", "start": "Create my smart home", From ba1abedb32527c04fec9fe3a3eeb1ec57882e856 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Mon, 4 Nov 2024 15:55:06 +0100 Subject: [PATCH 21/33] Remove compress from landing-page build --- build-scripts/gulp/compress.js | 16 ---------------- build-scripts/gulp/landing-page.js | 4 +--- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js index 6a6f931b60cf..fa171c9154b5 100644 --- a/build-scripts/gulp/compress.js +++ b/build-scripts/gulp/compress.js @@ -51,12 +51,6 @@ const compressHassioBrotli = () => paths.hassio_output_latest, false ); -const compressLandingPageBrotli = () => - compressDistBrotli( - paths.landingPage_output_root, - paths.landingPage_output_latest, - false - ); const compressAppZopfli = () => compressDistZopfli(paths.app_output_root, paths.app_output_latest); @@ -66,19 +60,9 @@ const compressHassioZopfli = () => paths.hassio_output_latest, true ); -const compressLandingPageZopfli = () => - compressDistZopfli( - paths.landingPage_output_root, - paths.landingPage_output_latest, - true - ); gulp.task("compress-app", gulp.parallel(compressAppBrotli, compressAppZopfli)); gulp.task( "compress-hassio", gulp.parallel(compressHassioBrotli, compressHassioZopfli) ); -gulp.task( - "compress-landing-page", - gulp.parallel(compressLandingPageBrotli, compressLandingPageZopfli) -); diff --git a/build-scripts/gulp/landing-page.js b/build-scripts/gulp/landing-page.js index f193bc885bd7..3a8f38749354 100644 --- a/build-scripts/gulp/landing-page.js +++ b/build-scripts/gulp/landing-page.js @@ -1,5 +1,4 @@ import gulp from "gulp"; -import env from "../env.cjs"; import "./clean.js"; import "./compress.js"; import "./entry-html.js"; @@ -37,7 +36,6 @@ gulp.task( "build-locale-data", "copy-static-landing-page", "webpack-prod-landing-page", - "gen-pages-landing-page-prod", - ...(env.isTestBuild() ? [] : ["compress-landing-page"]) // Don't compress running tests + "gen-pages-landing-page-prod" ) ); From bb1e86e97b34ccb644ccef291392c1f3af60f6da Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 19 Nov 2024 11:19:45 +0100 Subject: [PATCH 22/33] Remove .eslintrc from landing-page --- landing-page/.eslintrc.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 landing-page/.eslintrc.json diff --git a/landing-page/.eslintrc.json b/landing-page/.eslintrc.json deleted file mode 100644 index 659ccca5615d..000000000000 --- a/landing-page/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../.eslintrc.json" -} From b4ed00ec1e953f1ba3fe919409338ba52dd89dbd Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 19 Nov 2024 12:53:35 +0100 Subject: [PATCH 23/33] Fix landing-page CI translation issue --- landing-page/rollup.config.js | 10 ---------- landing-page/src/components/landing-page-logs.ts | 2 +- src/translations/en.json | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) delete mode 100644 landing-page/rollup.config.js diff --git a/landing-page/rollup.config.js b/landing-page/rollup.config.js deleted file mode 100644 index d316ad8aebba..000000000000 --- a/landing-page/rollup.config.js +++ /dev/null @@ -1,10 +0,0 @@ -import rollup from "../build-scripts/rollup.cjs"; -import env from "../build-scripts/env.cjs"; - -const config = rollup.createLandingPageConfig({ - isProdBuild: env.isProdBuild(), - latestBuild: true, - isStatsBuild: env.isStatsBuild(), -}); - -export default { ...config.inputOptions, output: config.outputOptions }; diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index f54347c64e0f..edc18d5a703b 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -61,7 +61,7 @@ class LandingPageLogs extends LitElement { ${this._show ? html`` diff --git a/src/translations/en.json b/src/translations/en.json index 7875e301f7dc..3db8fb415881 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7525,7 +7525,7 @@ "scroll_down_button": "New logs - Click to scroll", "fetch_error": "Failed to fetch logs", "retry": "Retry", - "download_full_log": "[%key:ui::panel::config::logs::download_full_log%]" + "download_logs": "[%key:ui::panel::config::logs::download_logs%]" }, "error_title": "Error installing Home Assistant", "error_description": "An error occurred while installing Home Assistant. Please check the logs for more information.", From 48886c698ba91802a38f8a172958e7c046f249f1 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 19 Nov 2024 13:16:53 +0100 Subject: [PATCH 24/33] Remove landing-page supervisor logs --- .../src/components/landing-page-logs.ts | 69 +++---------------- .../src/components/landing-page-network.ts | 40 ++--------- landing-page/src/data/observer.ts | 3 + landing-page/src/data/supervisor.ts | 45 +++++++++--- 4 files changed, 54 insertions(+), 103 deletions(-) create mode 100644 landing-page/src/data/observer.ts diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index edc18d5a703b..814739abc6f1 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -15,7 +15,7 @@ import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-ansi-to-html"; import "../../../src/components/ha-alert"; import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; -import { getObserverLogs, getSupervisorLogsFollow } from "../data/supervisor"; +import { getObserverLogs } from "../data/observer"; import { fireEvent } from "../../../src/common/dom/fire_event"; import { fileDownload } from "../../../src/util/file_download"; @@ -73,7 +73,7 @@ class LandingPageLogs extends LitElement { alert-type="error" .title=${this.localize("logs.fetch_error")} > - + ${this.localize("logs.retry")} @@ -113,7 +113,7 @@ class LandingPageLogs extends LitElement { this._scrolledToBottomController.observe(this._scrollBottomMarkerElement!); - this._startLogStream(); + this._loadObserverLogs(); } protected updated(changedProps: PropertyValues): void { @@ -139,20 +139,13 @@ class LandingPageLogs extends LitElement { } } - private _writeChunk(chunk: string, tempLogLine = ""): string { - const showError = ERROR_CHECK.test(chunk); + private _displayLogs(logs: string) { + this._ansiToHtmlElement?.clear(); - const scrolledToBottom = this._scrolledToBottomController.value; - const lines = `${tempLogLine}${chunk}` - .split("\n") - .filter((line) => line.trim() !== ""); + const showError = ERROR_CHECK.test(logs); - // handle edge case where the last line is not complete - if (chunk.endsWith("\n")) { - tempLogLine = ""; - } else { - tempLogLine = lines.splice(-1, 1)[0]; - } + const scrolledToBottom = this._scrolledToBottomController.value; + const lines = logs.split("\n").filter((line) => line.trim() !== ""); if (lines.length) { this._ansiToHtmlElement?.parseLinesToColoredPre(lines); @@ -168,45 +161,6 @@ class LandingPageLogs extends LitElement { } else { this._newLogsIndicator = true; } - - return tempLogLine; - } - - private async _startLogStream() { - this._error = false; - this._newLogsIndicator = false; - this._ansiToHtmlElement?.clear(); - - try { - const response = await getSupervisorLogsFollow(); - - if (!response.ok || !response.body) { - throw new Error("No stream body found"); - } - - let tempLogLine = ""; - - const reader = response.body.getReader(); - const decoder = new TextDecoder(); - let done = false; - - while (!done) { - // eslint-disable-next-line no-await-in-loop - const { value, done: readerDone } = await reader.read(); - done = readerDone; - - if (value) { - const chunk = decoder.decode(value, { stream: !done }); - tempLogLine = this._writeChunk(chunk, tempLogLine); - } - } - } catch (err: any) { - // eslint-disable-next-line no-console - console.error(err); - - // fallback to observerlogs if there is a problem with supervisor - this._loadObserverLogs(); - } } private _scheduleObserverLogs() { @@ -225,7 +179,7 @@ class LandingPageLogs extends LitElement { if (this._observerLogs !== logs) { this._observerLogs = logs; - this._writeChunk(logs); + this._displayLogs(logs); } this._scheduleObserverLogs(); @@ -238,10 +192,7 @@ class LandingPageLogs extends LitElement { private async _downloadFullLog() { const timeString = new Date().toISOString().replace(/:/g, "-"); - fileDownload( - "/supervisor/supervisor/logs?lines=1000", - `home-assistant_${timeString}.log` - ); + fileDownload("/observer/logs", `home-assistant_${timeString}.log`); } static styles = [ diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts index 94a12f0cc5c5..2857a2c2c787 100644 --- a/landing-page/src/components/landing-page-network.ts +++ b/landing-page/src/components/landing-page-network.ts @@ -14,28 +14,15 @@ import type { } from "../../../src/common/translations/localize"; import "../../../src/components/ha-button"; import "../../../src/components/ha-alert"; -import { getSupervisorNetworkInfo } from "../data/supervisor"; +import { + ALTERNATIVE_DNS_SERVERS, + getSupervisorNetworkInfo, + setSupervisorNetworkDns, +} from "../data/supervisor"; import { fireEvent } from "../../../src/common/dom/fire_event"; const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5; -const ALTERNATIVE_DNS_SERVERS: { - ipv4: string[]; - ipv6: string[]; - translationKey: LandingPageKeys; -}[] = [ - { - ipv4: ["1.1.1.1", "1.0.0.1"], - ipv6: ["2606:4700:4700::1111", "2606:4700:4700::1001"], - translationKey: "network_issue.use_cloudflare", - }, - { - ipv4: ["8.8.8.8", "8.8.4.4"], - ipv6: ["2001:4860:4860::8888", "2001:4860:4860::8844"], - translationKey: "network_issue.use_google", - }, -]; - @customElement("landing-page-network") class LandingPageNetwork extends LitElement { @property({ attribute: false }) @@ -145,22 +132,7 @@ class LandingPageNetwork extends LitElement { private async _setDns(ev) { const index = ev.target?.index; try { - const response = await fetch("/supervisor/network/dns", { - method: "POST", - body: JSON.stringify({ - ipv4: { - method: "auto", - nameservers: ALTERNATIVE_DNS_SERVERS[index].ipv4, - }, - ipv6: { - method: "auto", - nameservers: ALTERNATIVE_DNS_SERVERS[index].ipv6, - }, - }), - headers: { - "Content-Type": "application/json", - }, - }); + const response = await setSupervisorNetworkDns(index); if (!response.ok) { throw new Error("Failed to set DNS"); } diff --git a/landing-page/src/data/observer.ts b/landing-page/src/data/observer.ts new file mode 100644 index 000000000000..5b2be1bd85c2 --- /dev/null +++ b/landing-page/src/data/observer.ts @@ -0,0 +1,3 @@ +export async function getObserverLogs() { + return fetch("/observer/logs"); +} diff --git a/landing-page/src/data/supervisor.ts b/landing-page/src/data/supervisor.ts index 1e7018fe0f6d..c9bba89bcefb 100644 --- a/landing-page/src/data/supervisor.ts +++ b/landing-page/src/data/supervisor.ts @@ -1,15 +1,40 @@ -export async function getSupervisorLogsFollow() { - return fetch("/supervisor/supervisor/logs/follow?lines=500", { - headers: { - Accept: "text/plain", - }, - }); -} +import type { LandingPageKeys } from "../../../src/common/translations/localize"; -export async function getObserverLogs() { - return fetch("/observer/logs"); -} +export const ALTERNATIVE_DNS_SERVERS: { + ipv4: string[]; + ipv6: string[]; + translationKey: LandingPageKeys; +}[] = [ + { + ipv4: ["1.1.1.1", "1.0.0.1"], + ipv6: ["2606:4700:4700::1111", "2606:4700:4700::1001"], + translationKey: "network_issue.use_cloudflare", + }, + { + ipv4: ["8.8.8.8", "8.8.4.4"], + ipv6: ["2001:4860:4860::8888", "2001:4860:4860::8844"], + translationKey: "network_issue.use_google", + }, +]; export async function getSupervisorNetworkInfo() { return fetch("/supervisor/network/info"); } + +export const setSupervisorNetworkDns = async (dnsServerIndex: number) => + fetch("/supervisor/network/dns", { + method: "POST", + body: JSON.stringify({ + ipv4: { + method: "auto", + nameservers: ALTERNATIVE_DNS_SERVERS[dnsServerIndex].ipv4, + }, + ipv6: { + method: "auto", + nameservers: ALTERNATIVE_DNS_SERVERS[dnsServerIndex].ipv6, + }, + }), + headers: { + "Content-Type": "application/json", + }, + }); From dc83b6bad8f5a196abf9659ce220e9f156d0769d Mon Sep 17 00:00:00 2001 From: Wendelin Date: Tue, 19 Nov 2024 13:18:01 +0100 Subject: [PATCH 25/33] Fix onboarding-welcome-links LocalizeFunc type --- src/onboarding/onboarding-welcome-links.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/onboarding/onboarding-welcome-links.ts b/src/onboarding/onboarding-welcome-links.ts index 026889a0a91f..95183d8839ac 100644 --- a/src/onboarding/onboarding-welcome-links.ts +++ b/src/onboarding/onboarding-welcome-links.ts @@ -13,7 +13,7 @@ import "./onboarding-welcome-link"; class OnboardingWelcomeLinks extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) public localize!: LocalizeFunc; + @property({ attribute: false }) public localize!: LocalizeFunc; @property({ type: Boolean }) public mobileApp = false; From 51978f89294bf9aff90ccb1ce4e2abb0bef67d19 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 10:30:20 +0100 Subject: [PATCH 26/33] Add landing-page log streaming --- .../src/components/landing-page-logs.ts | 79 +++++++++++++++++-- landing-page/src/data/supervisor.ts | 16 ++++ 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index 814739abc6f1..94d6c1bf36ad 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -18,6 +18,7 @@ import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; import { getObserverLogs } from "../data/observer"; import { fireEvent } from "../../../src/common/dom/fire_event"; import { fileDownload } from "../../../src/util/file_download"; +import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor"; const ERROR_CHECK = /^[\d -:]+(ERROR|CRITICAL)(.*)/gm; declare global { @@ -26,6 +27,8 @@ declare global { } } +const SCHEDULE_FETCH_OBSERVER_LOGS = 5; + @customElement("landing-page-logs") class LandingPageLogs extends LitElement { @property({ attribute: false }) @@ -73,7 +76,7 @@ class LandingPageLogs extends LitElement { alert-type="error" .title=${this.localize("logs.fetch_error")} > - + ${this.localize("logs.retry")} @@ -113,7 +116,7 @@ class LandingPageLogs extends LitElement { this._scrolledToBottomController.observe(this._scrollBottomMarkerElement!); - this._loadObserverLogs(); + this._startLogStream(); } protected updated(changedProps: PropertyValues): void { @@ -139,13 +142,24 @@ class LandingPageLogs extends LitElement { } } - private _displayLogs(logs: string) { - this._ansiToHtmlElement?.clear(); + private _displayLogs(logs: string, tempLogLine = "", clear = false): string { + if (clear) { + this._ansiToHtmlElement?.clear(); + } const showError = ERROR_CHECK.test(logs); const scrolledToBottom = this._scrolledToBottomController.value; - const lines = logs.split("\n").filter((line) => line.trim() !== ""); + const lines = `${tempLogLine}${logs}` + .split("\n") + .filter((line) => line.trim() !== ""); + + // handle edge case where the last line is not complete + if (logs.endsWith("\n")) { + tempLogLine = ""; + } else { + tempLogLine = lines.splice(-1, 1)[0]; + } if (lines.length) { this._ansiToHtmlElement?.parseLinesToColoredPre(lines); @@ -161,10 +175,61 @@ class LandingPageLogs extends LitElement { } else { this._newLogsIndicator = true; } + + return tempLogLine; + } + + private async _startLogStream() { + this._error = false; + this._newLogsIndicator = false; + this._ansiToHtmlElement?.clear(); + + try { + const response = await getSupervisorLogsFollow(); + + if (!response.ok || !response.body) { + throw new Error("No stream body found"); + } + + let tempLogLine = ""; + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let done = false; + + while (!done) { + // eslint-disable-next-line no-await-in-loop + const { value, done: readerDone } = await reader.read(); + done = readerDone; + + if (value) { + const chunk = decoder.decode(value, { stream: !done }); + tempLogLine = this._displayLogs(chunk, tempLogLine); + } + } + } catch (err: any) { + // eslint-disable-next-line no-console + console.error(err); + + // fallback to observerlogs if there is a problem with supervisor + this._loadObserverLogs(); + } } private _scheduleObserverLogs() { - setTimeout(() => this._loadObserverLogs(), 5000); + setTimeout(async () => { + try { + // check if supervisor logs are available + const superVisorLogsResponse = await getSupervisorLogs(1); + if (superVisorLogsResponse.ok) { + this._startLogStream(); + return; + } + } catch (err) { + // ignore and continue with observer logs + } + this._loadObserverLogs(); + }, SCHEDULE_FETCH_OBSERVER_LOGS * 1000); } private async _loadObserverLogs() { @@ -179,7 +244,7 @@ class LandingPageLogs extends LitElement { if (this._observerLogs !== logs) { this._observerLogs = logs; - this._displayLogs(logs); + this._displayLogs(logs, "", true); } this._scheduleObserverLogs(); diff --git a/landing-page/src/data/supervisor.ts b/landing-page/src/data/supervisor.ts index c9bba89bcefb..a2f78d3676ed 100644 --- a/landing-page/src/data/supervisor.ts +++ b/landing-page/src/data/supervisor.ts @@ -17,6 +17,22 @@ export const ALTERNATIVE_DNS_SERVERS: { }, ]; +export async function getSupervisorLogs(lines = 100) { + return fetch(`/supervisor/supervisor/logs?lines=${lines}`, { + headers: { + Accept: "text/plain", + }, + }); +} + +export async function getSupervisorLogsFollow(lines = 500) { + return fetch(`/supervisor/supervisor/logs/follow?lines=${lines}`, { + headers: { + Accept: "text/plain", + }, + }); +} + export async function getSupervisorNetworkInfo() { return fetch("/supervisor/network/info"); } From 5c0dbaff0e183a7b49610598cbe295fdb72be893 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 10:42:21 +0100 Subject: [PATCH 27/33] Fix landing-page-logs error-check regex - github-advanced-security --- landing-page/src/components/landing-page-logs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index 94d6c1bf36ad..8c530ab2fb67 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -20,7 +20,7 @@ import { fireEvent } from "../../../src/common/dom/fire_event"; import { fileDownload } from "../../../src/util/file_download"; import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor"; -const ERROR_CHECK = /^[\d -:]+(ERROR|CRITICAL)(.*)/gm; +const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm; declare global { interface HASSDomEvents { "landing-page-error": undefined; From 3b3a32f682050c8ed2923db7f3e5952577fc3a56 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 13:25:58 +0100 Subject: [PATCH 28/33] Add landing-page dns error alert, improve try catch --- .../src/components/landing-page-network.ts | 45 +++++++++++-------- src/translations/en.json | 5 ++- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts index 2857a2c2c787..9842d92b2876 100644 --- a/landing-page/src/components/landing-page-network.ts +++ b/landing-page/src/components/landing-page-network.ts @@ -20,6 +20,7 @@ import { setSupervisorNetworkDns, } from "../data/supervisor"; import { fireEvent } from "../../../src/common/dom/fire_event"; +import { showAlertDialog } from "../../../src/dialogs/generic/show-dialog-box"; const SCHEDULE_FETCH_NETWORK_INFO_SECONDS = 5; @@ -93,34 +94,36 @@ class LandingPageNetwork extends LitElement { } private async _fetchSupervisorInfo() { + let data; try { const response = await getSupervisorNetworkInfo(); if (!response.ok) { throw new Error("Failed to fetch network info"); } - const { data } = await response.json(); - - this._getNetworkInfoError = false; - - if (!data.host_internet) { - this._networkIssue = true; - const primaryInterface = data.interfaces.find( - (intf) => intf.primary && intf.enabled - ); - if (primaryInterface) { - this._dnsPrimaryInterface = [ - ...(primaryInterface.ipv4?.nameservers || []), - ...(primaryInterface.ipv6?.nameservers || []), - ].join(", "); - } - } else { - this._networkIssue = false; - } + ({ data } = await response.json()); } catch (err) { // eslint-disable-next-line no-console console.error(err); this._getNetworkInfoError = true; + return; + } + + this._getNetworkInfoError = false; + + if (!data.host_internet) { + this._networkIssue = true; + const primaryInterface = data.interfaces.find( + (intf) => intf.primary && intf.enabled + ); + if (primaryInterface) { + this._dnsPrimaryInterface = [ + ...(primaryInterface.ipv4?.nameservers || []), + ...(primaryInterface.ipv6?.nameservers || []), + ].join(", "); + } + } else { + this._networkIssue = false; } fireEvent(this, "value-changed", { @@ -140,7 +143,11 @@ class LandingPageNetwork extends LitElement { } catch (err) { // eslint-disable-next-line no-console console.error(err); - this._getNetworkInfoError = true; + showAlertDialog(this, { + title: this.localize("network_issue.failed"), + text: this.localize("network_issue.set_dns_failed"), + confirmText: this.localize("network_issue.close"), + }); } } diff --git a/src/translations/en.json b/src/translations/en.json index 3db8fb415881..b1486a5a7f32 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7519,7 +7519,10 @@ "resolve_different": "To resolve this, you can try a different DNS server. Select one of the options below. Alternatively, change your router configuration to use your own custom DNS server.", "use_cloudflare": "Use Cloudflare DNS", "use_google": "Use Google DNS", - "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!" + "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!", + "failed": "Failed", + "set_dns_failed": "An error occurred while setting the DNS server. Please check the logs for more information and try again.", + "close": "[%key:ui::common::close%]" }, "logs": { "scroll_down_button": "New logs - Click to scroll", From 9aef5463bc3ca9eb6ea7b7f5f20f35885f9c762e Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 13:30:30 +0100 Subject: [PATCH 29/33] Fix landing-page keep show installer when _getNetworkInfoError --- landing-page/src/components/landing-page-network.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts index 9842d92b2876..814a998a3973 100644 --- a/landing-page/src/components/landing-page-network.ts +++ b/landing-page/src/components/landing-page-network.ts @@ -127,7 +127,7 @@ class LandingPageNetwork extends LitElement { } fireEvent(this, "value-changed", { - value: this._networkIssue || this._getNetworkInfoError, + value: this._networkIssue, }); this._scheduleFetchSupervisorInfo(); } From 87ad9fd196b7fca0ad5f80dbc948267397447f29 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 13:45:07 +0100 Subject: [PATCH 30/33] Fix landing-page download logs --- .../src/components/landing-page-logs.ts | 35 +++++++++++++------ landing-page/src/data/observer.ts | 2 ++ landing-page/src/data/supervisor.ts | 4 +++ 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index 8c530ab2fb67..5553750cf802 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -15,10 +15,17 @@ import "../../../src/components/ha-svg-icon"; import "../../../src/components/ha-ansi-to-html"; import "../../../src/components/ha-alert"; import type { HaAnsiToHtml } from "../../../src/components/ha-ansi-to-html"; -import { getObserverLogs } from "../data/observer"; +import { + getObserverLogs, + downloadUrl as observerLogsDownloadUrl, +} from "../data/observer"; import { fireEvent } from "../../../src/common/dom/fire_event"; import { fileDownload } from "../../../src/util/file_download"; -import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor"; +import { + getDownloadUrl, + getSupervisorLogs, + getSupervisorLogsFollow, +} from "../data/supervisor"; const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm; declare global { @@ -54,7 +61,9 @@ class LandingPageLogs extends LitElement { @state() private _newLogsIndicator?: boolean; - @state() private _observerLogs = ""; + @state() private _logLinesCount = 0; + + @state() private _observerFallback = false; protected render() { return html` @@ -66,7 +75,7 @@ class LandingPageLogs extends LitElement { ? html`` : nothing}
@@ -145,6 +154,7 @@ class LandingPageLogs extends LitElement { private _displayLogs(logs: string, tempLogLine = "", clear = false): string { if (clear) { this._ansiToHtmlElement?.clear(); + this._logLinesCount = 0; } const showError = ERROR_CHECK.test(logs); @@ -163,6 +173,7 @@ class LandingPageLogs extends LitElement { if (lines.length) { this._ansiToHtmlElement?.parseLinesToColoredPre(lines); + this._logLinesCount += lines.length; } if (showError) { @@ -180,6 +191,7 @@ class LandingPageLogs extends LitElement { } private async _startLogStream() { + this._observerFallback = false; this._error = false; this._newLogsIndicator = false; this._ansiToHtmlElement?.clear(); @@ -212,6 +224,7 @@ class LandingPageLogs extends LitElement { console.error(err); // fallback to observerlogs if there is a problem with supervisor + this._observerFallback = true; this._loadObserverLogs(); } } @@ -242,10 +255,7 @@ class LandingPageLogs extends LitElement { const logs = await response.text(); - if (this._observerLogs !== logs) { - this._observerLogs = logs; - this._displayLogs(logs, "", true); - } + this._displayLogs(logs, "", true); this._scheduleObserverLogs(); } catch (err) { @@ -255,9 +265,14 @@ class LandingPageLogs extends LitElement { } } - private async _downloadFullLog() { + private _downloadLogs() { const timeString = new Date().toISOString().replace(/:/g, "-"); - fileDownload("/observer/logs", `home-assistant_${timeString}.log`); + const downloadUrl = this._observerFallback + ? observerLogsDownloadUrl + : getDownloadUrl(this._logLinesCount); + const downloadName = `${this._observerFallback ? "observer" : "supervisor"}_${timeString}.log`; + + fileDownload(downloadUrl, downloadName); } static styles = [ diff --git a/landing-page/src/data/observer.ts b/landing-page/src/data/observer.ts index 5b2be1bd85c2..93216304adb9 100644 --- a/landing-page/src/data/observer.ts +++ b/landing-page/src/data/observer.ts @@ -1,3 +1,5 @@ export async function getObserverLogs() { return fetch("/observer/logs"); } + +export const downloadUrl = "/observer/logs"; diff --git a/landing-page/src/data/supervisor.ts b/landing-page/src/data/supervisor.ts index a2f78d3676ed..ace051250eb8 100644 --- a/landing-page/src/data/supervisor.ts +++ b/landing-page/src/data/supervisor.ts @@ -17,6 +17,10 @@ export const ALTERNATIVE_DNS_SERVERS: { }, ]; +export function getDownloadUrl(lines = 500) { + return `/supervisor/supervisor/logs?lines=${lines}`; +} + export async function getSupervisorLogs(lines = 100) { return fetch(`/supervisor/supervisor/logs?lines=${lines}`, { headers: { From 1989850b960fdc898a79e965df32689117395e34 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 16:34:04 +0100 Subject: [PATCH 31/33] Use observer logs for download in landing-page --- landing-page/src/components/landing-page-logs.ts | 12 ++---------- landing-page/src/data/supervisor.ts | 4 ---- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index 5553750cf802..2c626e302ce4 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -21,11 +21,7 @@ import { } from "../data/observer"; import { fireEvent } from "../../../src/common/dom/fire_event"; import { fileDownload } from "../../../src/util/file_download"; -import { - getDownloadUrl, - getSupervisorLogs, - getSupervisorLogsFollow, -} from "../data/supervisor"; +import { getSupervisorLogs, getSupervisorLogsFollow } from "../data/supervisor"; const ERROR_CHECK = /^[\d\s-:]+(ERROR|CRITICAL)(.*)/gm; declare global { @@ -267,12 +263,8 @@ class LandingPageLogs extends LitElement { private _downloadLogs() { const timeString = new Date().toISOString().replace(/:/g, "-"); - const downloadUrl = this._observerFallback - ? observerLogsDownloadUrl - : getDownloadUrl(this._logLinesCount); - const downloadName = `${this._observerFallback ? "observer" : "supervisor"}_${timeString}.log`; - fileDownload(downloadUrl, downloadName); + fileDownload(observerLogsDownloadUrl, `observer_${timeString}.log`); } static styles = [ diff --git a/landing-page/src/data/supervisor.ts b/landing-page/src/data/supervisor.ts index ace051250eb8..a2f78d3676ed 100644 --- a/landing-page/src/data/supervisor.ts +++ b/landing-page/src/data/supervisor.ts @@ -17,10 +17,6 @@ export const ALTERNATIVE_DNS_SERVERS: { }, ]; -export function getDownloadUrl(lines = 500) { - return `/supervisor/supervisor/logs?lines=${lines}`; -} - export async function getSupervisorLogs(lines = 100) { return fetch(`/supervisor/supervisor/logs?lines=${lines}`, { headers: { From 8ad6873454472885f36ddfb88ca62efd1d608ea6 Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 16:49:15 +0100 Subject: [PATCH 32/33] Add error message to landing-page dns set fail --- landing-page/src/components/landing-page-network.ts | 7 +++++-- src/translations/en.json | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/landing-page/src/components/landing-page-network.ts b/landing-page/src/components/landing-page-network.ts index 814a998a3973..f4cfadd395e7 100644 --- a/landing-page/src/components/landing-page-network.ts +++ b/landing-page/src/components/landing-page-network.ts @@ -140,12 +140,15 @@ class LandingPageNetwork extends LitElement { throw new Error("Failed to set DNS"); } this._networkIssue = false; - } catch (err) { + } catch (err: any) { // eslint-disable-next-line no-console console.error(err); showAlertDialog(this, { title: this.localize("network_issue.failed"), - text: this.localize("network_issue.set_dns_failed"), + warning: true, + text: `${this.localize( + "network_issue.set_dns_failed" + )}${err?.message ? ` ${this.localize("network_issue.error")}: ${err.message}` : ""}`, confirmText: this.localize("network_issue.close"), }); } diff --git a/src/translations/en.json b/src/translations/en.json index b1486a5a7f32..82ef27db64a1 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -7522,6 +7522,7 @@ "no_primary_interface": "Home Assistant OS wasn't able to detect a primary network interface, so you cannot define a DNS server!", "failed": "Failed", "set_dns_failed": "An error occurred while setting the DNS server. Please check the logs for more information and try again.", + "error": "Error", "close": "[%key:ui::common::close%]" }, "logs": { From 9106d26bd31c585473da167e44b33386632085df Mon Sep 17 00:00:00 2001 From: Wendelin Date: Wed, 20 Nov 2024 17:04:34 +0100 Subject: [PATCH 33/33] Remove unused _observerFallback --- landing-page/src/components/landing-page-logs.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/landing-page/src/components/landing-page-logs.ts b/landing-page/src/components/landing-page-logs.ts index 2c626e302ce4..80fe1945d260 100644 --- a/landing-page/src/components/landing-page-logs.ts +++ b/landing-page/src/components/landing-page-logs.ts @@ -59,8 +59,6 @@ class LandingPageLogs extends LitElement { @state() private _logLinesCount = 0; - @state() private _observerFallback = false; - protected render() { return html`
@@ -187,7 +185,6 @@ class LandingPageLogs extends LitElement { } private async _startLogStream() { - this._observerFallback = false; this._error = false; this._newLogsIndicator = false; this._ansiToHtmlElement?.clear(); @@ -220,7 +217,6 @@ class LandingPageLogs extends LitElement { console.error(err); // fallback to observerlogs if there is a problem with supervisor - this._observerFallback = true; this._loadObserverLogs(); } }