Merge pull request #482 from bastienwirtz/vuejs-3

Vuejs 3
This commit is contained in:
Bastien Wirtz 2022-07-13 13:46:03 -07:00 committed by GitHub
commit c11c45a661
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2146 additions and 7829 deletions

View File

@ -1,3 +0,0 @@
> 1%
last 2 versions
not dead

17
.eslintrc.cjs Normal file
View File

@ -0,0 +1,17 @@
/* eslint-env node */
require("@rushstack/eslint-patch/modern-module-resolution");
module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-prettier",
],
env: {
"vue/setup-compiler-macros": true,
},
rules: {
"vue/multi-word-component-names": "off",
},
};

View File

@ -1,15 +0,0 @@
module.exports = {
root: true,
env: {
node: true,
},
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
parserOptions: {
parser: "babel-eslint",
},
rules: {
"no-console": "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
"vue/require-v-for-key": "off",
},
};

View File

@ -20,12 +20,19 @@ jobs:
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps: steps:
- uses: actions/checkout@v3 -
- name: Use Node.js ${{ matrix.node-version }} name: Checkout
uses: actions/checkout@v3
-
name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2 uses: actions/setup-node@v2
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
cache: 'yarn' cache: 'yarn'
- run: yarn install -
- run: yarn lint name: install dependencies
run: yarn install
-
name: Check code style & potentential issues
run: yarn lint

View File

@ -10,15 +10,20 @@ jobs:
name: Upload Release Asset name: Upload Release Asset
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 -
- name: Build project name: Checkout
uses: actions/checkout@v3
-
name: Build project
run: | run: |
yarn install yarn install
yarn build yarn build
- name: Create artifact -
name: Create artifact
working-directory: "dist" working-directory: "dist"
run: zip -r ../homer.zip ./* run: zip -r ../homer.zip ./*
- name: Create Release -
name: Create Release
id: create_release id: create_release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:

View File

@ -1,3 +0,0 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

View File

@ -152,7 +152,7 @@ services:
# background: red # optional color for card to set color directly without custom stylesheet # background: red # optional color for card to set color directly without custom stylesheet
``` ```
View [Custom Services](customservices.md) for details about all available custom services (like PiHole) and how to configure them. View **[Custom Services](customservices.md)** for details about all available custom services (like `PiHole`) and how to configure them.
If you choose to fetch message information from an endpoint, the output format should be as follows (or you can [custom map fields as shown in tips-and-tricks](./tips-and-tricks.md#mapping-fields)): If you choose to fetch message information from an endpoint, the output format should be as follows (or you can [custom map fields as shown in tips-and-tricks](./tips-and-tricks.md#mapping-fields)):
@ -180,27 +180,4 @@ You can read the [bulma modifiers page](https://bulma.io/documentation/modifiers
## PWA Icons ## PWA Icons
In order to easily generate all required icon preset for the PWA to work, a tool like [vue-pwa-asset-generator](https://www.npmjs.com/package/vue-pwa-asset-generator) can be used: See icons documentation [here](https://github.com/bastienwirtz/homer/blob/main/public/assets/icons/README.md).
```bash
npx vue-pwa-asset-generator -a {your_512x512_source_png} -o {your_output_folder}
```
## Supported services
Currently the following services are supported for showing quick infos on the card. They can be used by setting the type to one of the following values at the item.
- PiHole
- AdGuardHome
- PaperlessNG
- Mealie
## Additional configuration
### Paperless
For Paperless you need an API-Key which you have to store at the item in the field `apikey`.
### Mealie
First off make sure to remove an existing `subtitle` as it will take precedence if set. Setting `type: "Mealie"` will then show the number of recipes Mealie is keeping organized or the planned meal for today if one is planned. You will have to set an API key in the field `apikey` which can be created in your Mealie installation.

View File

@ -19,6 +19,7 @@ within Homer:
+ [Emby / Jellyfin](#emby--jellyfin) + [Emby / Jellyfin](#emby--jellyfin)
+ [Uptime Kuma](#uptime-kuma) + [Uptime Kuma](#uptime-kuma)
+ [Tautulli](#tautulli) + [Tautulli](#tautulli)
+ [Mealie](#mealie)
+ [Healthchecks](#healthchecks) + [Healthchecks](#healthchecks)
If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page. If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page.
@ -225,6 +226,11 @@ endpoint pointing to Tautulli!
apikey: "MY-SUPER-SECRET-API-KEY" apikey: "MY-SUPER-SECRET-API-KEY"
``` ```
## Mealie
First off make sure to remove an existing `subtitle` as it will take precedence if set.
Setting `type: "Mealie"` will then show the number of recipes Mealie is keeping organized or the planned meal for today if one is planned. You will have to set an API key in the field `apikey` which can be created in your Mealie installation.
## Healthchecks ## Healthchecks
This service displays information about the configured status checks from the Healthchecks application. This service displays information about the configured status checks from the Healthchecks application.

View File

@ -5,11 +5,11 @@ If you want to contribute to Homer, please read the [contributing guidelines](ht
```sh ```sh
# Using yarn (recommended) # Using yarn (recommended)
yarn install yarn install
yarn serve yarn dev
# **OR** Using npm # **OR** Using npm
npm install npm install
npm run serve npm run dev
``` ```
## Custom services ## Custom services

15
index.html Normal file
View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="assets/icons/favicon.ico" />
<link rel="apple-touch-icon" href="assets/icons/apple-touch-icon.png" sizes="180x180">
<link rel="mask-icon" href="assets/icons/logo.svg">
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
<title>Homer</title>
</head>
<body>
<div id="app-mount"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@ -1,35 +1,29 @@
{ {
"name": "homer", "name": "homer",
"version": "21.09.1", "version": "22.07.2",
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "dev": "vite",
"build": "vue-cli-service build", "build": "vite build",
"lint": "vue-cli-service lint" "preview": "vite preview --port 5050",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.1.1", "@fortawesome/fontawesome-free": "^6.1.1",
"bulma": "^0.9.4", "bulma": "^0.9.4",
"core-js": "^3.22.7",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"register-service-worker": "^1.7.2", "vue": "^3.2.33"
"vue": "^2.6.14"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.5.19", "@rushstack/eslint-patch": "^1.1.0",
"@vue/cli-plugin-eslint": "~4.5.19", "@vitejs/plugin-vue": "^2.3.1",
"@vue/cli-plugin-pwa": "~4.5.19", "@vue/eslint-config-prettier": "^7.0.0",
"@vue/cli-service": "~4.5.19", "eslint": "^8.5.0",
"@vue/eslint-config-prettier": "^6.0.0", "eslint-plugin-vue": "^9.2.0",
"babel-eslint": "^10.1.0", "prettier": "^2.5.1",
"eslint": "^6.7.2", "sass": "^1.52.2",
"eslint-plugin-prettier": "^3.3.1", "vite": "^2.9.14",
"eslint-plugin-vue": "^6.2.2", "vite-plugin-pwa": "^0.12.3"
"prettier": "^2.2.1",
"raw-loader": "^4.0.2",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.12"
}, },
"license": "Apache-2.0" "license": "Apache-2.0"
} }

View File

@ -0,0 +1,12 @@
# PWA Icons / Images
We suggest you to create a svg or png icon (if it is a png icon, with the maximum resolution possible) for your application and use it to generate a favicon package in [Favicon Generator](https://realfavicongenerator.net/).
Once generated, download the ZIP and use android-* icons for pwa-*:
- use `android-chrome-192x192.png` for `pwa-192x192.png`
- use `android-chrome-512x512.png` for `pwa-512x512.png`
- `apple-touch-icon.png` is `apple-touch-icon.png`
- `favicon.ico` is `favicon.ico`
`

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,42 +0,0 @@
{
"name": "Homer Dashboard",
"short_name": "Homer",
"theme_color": "#3367D6",
"start_url": "../",
"icons": [
{
"src": "./icons/favicon-16x16.png",
"sizes": "16x16",
"type": "image/png"
},
{
"src": "./icons/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "./icons/icon-any.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "./icons/icon-any.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "./icons/icon-maskable.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "./icons/safari-pinned-tab.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "monochrome"
}
]
}

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
<meta name="robots" content="noindex">
<link rel="icon" href="<%= BASE_URL %>favicon.png">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -1,2 +0,0 @@
User-agent: *
Disallow:

View File

@ -49,10 +49,10 @@
<SearchInput <SearchInput
class="navbar-item is-inline-block-mobile" class="navbar-item is-inline-block-mobile"
:hotkey="searchHotkey()" :hotkey="searchHotkey()"
@input="filterServices" @input="filterServices($event.target?.value)"
@search-focus="showMenu = true" @search-focus="showMenu = true"
@search-open="navigateToFirstService" @search-open="navigateToFirstService($event?.target?.value)"
@search-cancel="filterServices" @search-cancel="filterServices()"
/> />
</Navbar> </Navbar>
</div> </div>
@ -140,8 +140,8 @@
</template> </template>
<script> <script>
const jsyaml = require("js-yaml"); import jsyaml from "js-yaml";
const merge = require("lodash.merge"); import merge from "lodash.merge";
import Navbar from "./components/Navbar.vue"; import Navbar from "./components/Navbar.vue";
import GetStarted from "./components/GetStarted.vue"; import GetStarted from "./components/GetStarted.vue";
@ -153,7 +153,7 @@ import SettingToggle from "./components/SettingToggle.vue";
import DarkMode from "./components/DarkMode.vue"; import DarkMode from "./components/DarkMode.vue";
import DynamicTheme from "./components/DynamicTheme.vue"; import DynamicTheme from "./components/DynamicTheme.vue";
import defaultConfig from "./assets/defaults.yml"; import defaultConfig from "./assets/defaults.yml?raw";
export default { export default {
name: "App", name: "App",
@ -255,11 +255,12 @@ export default {
}); });
}, },
matchesFilter: function (item) { matchesFilter: function (item) {
const needle = this.filter?.toLowerCase();
return ( return (
item.name.toLowerCase().includes(this.filter) || item.name.toLowerCase().includes(needle) ||
(item.subtitle && item.subtitle.toLowerCase().includes(this.filter)) || (item.subtitle && item.subtitle.toLowerCase().includes(needle)) ||
(item.tag && item.tag.toLowerCase().includes(this.filter)) || (item.tag && item.tag.toLowerCase().includes(needle)) ||
(item.keywords && item.keywords.toLowerCase().includes(this.filter)) (item.keywords && item.keywords.toLowerCase().includes(needle))
); );
}, },
navigateToFirstService: function (target) { navigateToFirstService: function (target) {
@ -271,6 +272,7 @@ export default {
} }
}, },
filterServices: function (filter) { filterServices: function (filter) {
console.log(filter);
this.filter = filter; this.filter = filter;
if (!filter) { if (!filter) {

View File

@ -2,7 +2,7 @@
@import "./webfonts/webfonts.scss"; @import "./webfonts/webfonts.scss";
@import "bulma"; @import "../../node_modules/bulma/bulma";
// Themes import // Themes import
@import "./themes/sui.scss"; @import "./themes/sui.scss";
@ -13,7 +13,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
html, body, body #app { html, body, body #app-mount, body #app {
height: 100%; height: 100%;
background-color: var(--background); background-color: var(--background);
} }

View File

@ -56,7 +56,9 @@ export default {
// extra check to make sure we're not offline // extra check to make sure we're not offline
let that = this; let that = this;
const aliveCheckUrl = window.location.href + "?t=" + new Date().valueOf(); const aliveCheckUrl = `${window.location.origin}${
window.location.pathname
}/index.html?t=${new Date().valueOf()}`;
return fetch(aliveCheckUrl, { return fetch(aliveCheckUrl, {
method: "HEAD", method: "HEAD",
cache: "no-store", cache: "no-store",

View File

@ -1,6 +1,6 @@
<template> <template>
<a <a
v-on:click="toggleTheme()" @click="toggleTheme()"
aria-label="Toggle dark mode" aria-label="Toggle dark mode"
class="navbar-item is-inline-block-mobile" class="navbar-item is-inline-block-mobile"
> >

View File

@ -47,7 +47,6 @@ export default {
} }
if (this.item.url) { if (this.item.url) {
let fetchedMessage = await this.downloadMessage(this.item.url); let fetchedMessage = await this.downloadMessage(this.item.url);
console.log("done");
if (this.item.mapping) { if (this.item.mapping) {
fetchedMessage = this.mapRemoteMessage(fetchedMessage); fetchedMessage = this.mapRemoteMessage(fetchedMessage);
} }

View File

@ -75,7 +75,7 @@ export default {
this.$emit("input", value.toLowerCase()); this.$emit("input", value.toLowerCase());
}, },
}, },
beforeDestroy() { beforeUnmount() {
document.removeEventListener("keydown", this._keyListener); document.removeEventListener("keydown", this._keyListener);
}, },
}; };

View File

@ -1,8 +1,9 @@
<template> <template>
<component v-bind:is="component" :item="item" :proxy="proxy"></component> <component :is="component" :item="item" :proxy="proxy"></component>
</template> </template>
<script> <script>
import { defineAsyncComponent } from "vue";
import Generic from "./services/Generic.vue"; import Generic from "./services/Generic.vue";
export default { export default {
@ -17,7 +18,7 @@ export default {
if (type === "Generic") { if (type === "Generic") {
return Generic; return Generic;
} }
return () => import(`./services/${type}.vue`); return defineAsyncComponent(() => import(`./services/${type}.vue`));
}, },
}, },
}; };

View File

@ -1,5 +1,8 @@
<template> <template>
<a v-on:click="toggleSetting()" class="navbar-item is-inline-block-mobile"> <a
@click.prevent="toggleSetting()"
class="navbar-item is-inline-block-mobile"
>
<span><i :class="['fas', 'fa-fw', value ? icon : secondaryIcon]"></i></span> <span><i :class="['fas', 'fa-fw', value ? icon : secondaryIcon]"></i></span>
<slot></slot> <slot></slot>
</a> </a>

View File

@ -22,7 +22,7 @@
<div v-else> <div v-else>
<p class="title is-4">{{ name }}</p> <p class="title is-4">{{ name }}</p>
<p class="subtitle is-6"> <p class="subtitle is-6">
{{ temp | tempSuffix(this.item.units) }} {{ temperature }}
</p> </p>
</div> </div>
</div> </div>
@ -50,6 +50,19 @@ export default {
conditions: null, conditions: null,
error: false, error: false,
}), }),
computed: {
temperature: function () {
if (!this.temp) return "";
let unit = "K";
if (this.item.units === "metric") {
unit = "°C";
} else if (this.item.units === "imperial") {
unit = "°F";
}
return `${this.temp} ${unit}`;
},
},
created() { created() {
this.fetchWeather(); this.fetchWeather();
}, },
@ -86,19 +99,6 @@ export default {
}); });
}, },
}, },
filters: {
tempSuffix: function (value, type) {
if (!value) return "";
let unit = "K";
if (type === "metric") {
unit = "°C";
} else if (type === "imperial") {
unit = "°F";
}
return `${value} ${unit}`;
},
},
}; };
</script> </script>

View File

@ -99,6 +99,7 @@ export default {
}, },
}, },
created() { created() {
/* eslint-disable */
this.item.url = `${this.item.url}/status/${this.dashboard}`; this.item.url = `${this.item.url}/status/${this.dashboard}`;
this.fetchStatus(); this.fetchStatus();
}, },

View File

@ -1,19 +1,13 @@
import Vue from "vue"; import { createApp, h } from "vue";
import App from "./App.vue"; import App from "./App.vue";
import "./registerServiceWorker";
import "@fortawesome/fontawesome-free/css/all.css"; import "@fortawesome/fontawesome-free/css/all.css";
import "./assets/app.scss"; import "./assets/app.scss";
Vue.config.productionTip = false; const app = createApp(App);
Vue.component("DynamicStyle", { app.component("DynamicStyle", (_props, context) => {
render: function (createElement) { return h("style", {}, context.slots);
return createElement("style", this.$slots.default);
},
}); });
new Vue({ app.mount("#app-mount");
render: (h) => h(App),
}).$mount("#app");

View File

@ -1,34 +0,0 @@
/* eslint-disable no-console */
import { register } from "register-service-worker";
if (process.env.NODE_ENV === "production") {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
console.log(
"App is being served from cache by a service worker.\n" +
"For more details, visit https://goo.gl/AFskqB"
);
},
registered() {
console.log("Service worker has been registered.");
},
cached() {
console.log("Content has been cached for offline use.");
},
updatefound() {
console.log("New content is downloading.");
},
updated() {
console.log("New content is available; please refresh.");
},
offline() {
console.log(
"No internet connection found. App is running in offline mode."
);
},
error(error) {
console.error("Error during service worker registration:", error);
},
});
}

44
vite.config.js Normal file
View File

@ -0,0 +1,44 @@
import { VitePWA } from "vite-plugin-pwa";
import { fileURLToPath, URL } from "url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
// https://vitejs.dev/config/
export default defineConfig({
base: "",
build: {
assetsDir: "resources",
},
plugins: [
vue(),
VitePWA({
registerType: "autoUpdate",
useCredentials: true,
manifestFilename: "assets/manifest.json",
manifest: {
name: "Homer dashboard",
short_name: "Homer",
description: "Home Server Dashboard",
theme_color: "#3367D6",
icons: [
{
src: "./icons/pwa-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "./icons/pwa-512x512.png",
sizes: "512x512",
type: "image/png",
},
],
},
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});

View File

@ -1,32 +0,0 @@
const manifestOptions = require("./public/assets/manifest.json");
module.exports = {
chainWebpack: (config) => {
config.module
.rule("yaml")
.test(/\.ya?ml$/)
.use("raw-loader")
.loader("raw-loader")
.end();
},
publicPath: "",
pwa: {
manifestPath: "assets/manifest.json",
manifestCrossorigin: "use-credentials",
appleMobileWebAppStatusBarStyle: "black",
appleMobileWebAppCapable: "yes",
name: manifestOptions.name,
themeColor: manifestOptions.theme_color,
manifestOptions,
iconPaths: {
favicon32: "assets/icons/favicon-32x32.png",
favicon16: "assets/icons/favicon-16x16.png",
appleTouchIcon: "assets/icons/icon-maskable.png",
maskIcon: "assets/icons/safari-pinned-tab.svg",
msTileImage: "assets/icons/icon-any.png",
},
},
devServer: {
disableHostCheck: true,
},
};

9540
yarn.lock

File diff suppressed because it is too large Load Diff