1
0
mirror of https://github.com/taigrr/homer synced 2025-01-18 04:53:12 -08:00

Compare commits

..

No commits in common. "main" and "v20.09.1" have entirely different histories.

41 changed files with 1273 additions and 1677 deletions

View File

@ -12,7 +12,7 @@ Fixes # (issue)
## Checklist:
- [ ] I've read & comply with the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/master/CONTRIBUTING.md)
- [ ] I read & comply with the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/master/CONTRIBUTING.md)
- [ ] I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
- [ ] I have made corresponding changes the documentation (README.md).
- [ ] I've checked my modifications for any breaking changes, especially in the `config.yml` file
- [ ] I've check my modifications for any breaking change, especially in the `config.yml` file

View File

@ -6,10 +6,10 @@ First off, thank you for considering contributing to Homer!
### Project philosophy
Homer is meant to be a light and very simple dashboard that keeps all your useful utilities at hands. The few features implemented in Homer focus on
Homer is meant to be a light and very simple dashboard that keeps all your usefull utilities at hands. The few features implemented in Homer focus on
UX and usability. If you are looking for a full featured dashboard, there is tons of great stuff out there like https://heimdall.site/, https://github.com/rmountjoy92/DashMachine or https://organizr.app/.
- Configuration is stored in a simple config file, avoiding the need for a backend/database while making possible to use versioning or [config template](https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html).
- Configuration is stored in a simple config file, avoiding the need for a backend/database while making possible to use versionning or [config template](https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html).
- Only modern browsers are supported, feel free to use any JS features without any polyfill as soon as the latest version of the major browsers supports them.
### Roadmap
@ -21,7 +21,7 @@ Feel free to open an issue if you have any question.
### Code of conduct and guidelines
First of all, we expect everyone (contributors and maintainers alike) to respect the [Code of conduct](https://github.com/bastienwirtz/homer/blob/master/CODE_OF_CONDUCT.md). It is not a recommendation, it is mandatory.
First of all, we expect everyone (contributors and maintainers alike) to respect the [Code of conduct](https://github.com/bastienwirtz/homer/blob/master/CODE_OF_CONDUCT.md). It is not a recomandation, it is mandatory.
For all contributions, please respect the following guidelines:

View File

@ -2,7 +2,7 @@
<img
width="180"
alt="Homer's donut"
src="https://raw.githubusercontent.com//bastienwirtz/homer/main/public/logo.png">
src="https://raw.githubusercontent.com//bastienwirtz/homer/master/public/logo.png">
<br/>
Homer
</h1>
@ -36,7 +36,7 @@
</p>
<p align="center">
<img src="https://raw.github.com/bastienwirtz/homer/main/docs/screenshot.png" width="100%">
<img src="https://raw.github.com/bastienwirtz/homer/master/docs/screenshot.png" width="100%">
</p>
## Table of Contents
@ -45,7 +45,7 @@
- [Configuration](docs/configuration.md)
- [Tips & tricks](docs/tips-and-tricks.md)
- [Roadmap](#roadmap)
- [Development](docs/development.md)
- [Developement](docs/developement.md)
## Features

View File

@ -1,6 +1,6 @@
## Configuration
Title, icons, links, colors, and services can be configured in the `config.yml` file (located in `/assets` directory once built, or in the `public/assets` directory in development mode), using [yaml](http://yaml.org/) format.
Title, icons, links, colors, and services can be configured in the `config.yml` file (located in `/assets` directory once built, or in the `public/assets` directory in developement mode), using [yaml](http://yaml.org/) format.
```yaml
---
@ -22,12 +22,10 @@ header: true # Set to false to hide the header
footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it.
columns: "3" # "auto" or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)
vlayout: true # default to the vertical layout
connectivityCheck: true # whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example)
# Optional theming
theme: default # 'default' or one of the theme available in 'src/assets/themes'.
theme_use_dark: false # true or false, useful for overriding browser default in new sessions
# Optional custom stylesheet
# Will load custom CSS files. Especially useful for custom icon sets.
@ -70,7 +68,6 @@ message:
# url: "https://<my-api-endpoint>" # Can fetch information from an endpoint to override value below.
style: "is-warning"
title: "Optional message!"
icon: "fa fa-exclamation-triangle"
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
# Optional navbar
@ -89,7 +86,7 @@ links:
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
services:
- name: "Application"
icon: "fas fa-code-branch"
icon: "fa fa-code-fork"
items:
- name: "Awesome app"
logo: "assets/tools/sample.png"
@ -99,8 +96,6 @@ services:
tag: "app"
url: "https://www.reddit.com/r/selfhosted/"
target: "_blank" # optional html tag target attribute
info: "https://github.com/bastienwirtz/homer/tree/main/docs" # optional link to documentation
infotarget: "_blank" # same as target, but for icon link
- name: "Another one"
logo: "assets/tools/sample2.png"
subtitle: "Another application"
@ -111,15 +106,13 @@ services:
- name: "Other group"
icon: "fas fa-heartbeat"
items:
- name: "Pi-hole"
- name: "Another app"
logo: "assets/tools/sample.png"
subtitle: "Network-wide Ad Blocking"
subtitle: "Another example"
tag: "other"
url: "http://192.168.0.151/admin"
type: "PiHole" # optional, loads a specific component that provides extra features. MUST MATCH a file name (without file extension) available in `src/components/services`
url: "https://www.reddit.com/r/selfhosted/"
target: "_blank" # optional html a tag target attribute
# class: "green" # optional custom CSS class for card, useful with custom stylesheet
# background: red # optional color for card to set color directly without custom stylesheet
```
If you choose to fetch message information from an endpoint, the output format should be:

View File

@ -1,4 +1,4 @@
## Development
## Developement
```sh
# Using yarn (recommended)
@ -13,7 +13,7 @@ npm run serve
### Themes
Themes are meant to be simple customization (written in [scss](https://sass-lang.com/documentation/syntax)).
To add a new theme, just add a file in the theme directory, and put all style in the `body #app.theme-<name>` scope. Then import it in the main style file.
To addd a new theme, just add a file in the theme directory, and put all style in the `body #app.theme-<name>` scope. Then import it in the main style file.
```scss
// `src/assets/themes/my-awesome-theme.scss`

View File

@ -7,7 +7,7 @@ Here is a collection of neat tips and tricks that Homer users have come up with!
These extensions for [Firefox](https://addons.mozilla.org/firefox/addon/custom-new-tab-page) and [Chrome & Friends](https://chrome.google.com/webstore/detail/new-tab-changer/occbjkhimchkolibngmcefpjlbknggfh) allow you to have your homer dashboard in your new tab page, while leaving focus on the address bar meaning you can still type right away if you want to search or go to a page that is not on your homer dash.
The Firefox extension loads Homer in an iframe on your new tab page, meaning you have to add `target: '_top'` to each of your items.
The firefox extension loads Homer in an iframe on your new tab page, meaning you have to add `target: '_top'` to each of your items.
```yaml
- name: "Reddit"
@ -24,7 +24,7 @@ The Firefox extension loads Homer in an iframe on your new tab page, meaning you
## YAML Anchors
#### `by @JamiePhonic`
Since Homer is configured using YAML, it supports all of YAML's helpful features, such as anchoring!
Since Homer is configured using YAML, it supports all of YAML's helpful fetaures, such as anchoring!
For example, you can define tags and tag styles for each "item" in a service.
Using Anchoring, you can define all your tags and their styles once like this: (for example)
@ -66,13 +66,13 @@ Then when Homer reads your config, it will substitute your anchors automatically
target: "_blank" # optional html tag target attribute
```
The end result is that if you want to update the name or style of any particular tag, just update it once, in the tags section!
The end result is that if you want to update the name or style of any perticular tag, just update it once, in the tags section!
Great if you have a lot of services or a lot of tags!
## Remotely edit your config with Code Server
#### `by @JamiePhonic`
Homer doesn't yet provide a way to edit your configuration from inside Homer itself, but that doesn't mean it cant be done!
Homer doesn't yet provide a way to edit your configuration from inside Homer itself, but that doesnt mean it cant be done!
You can setup and use [Code-Server](https://github.com/cdr/code-server) to edit your `config.yml` file from anywhere!
@ -123,4 +123,4 @@ So, using [Node-Red](https://nodered.org/docs/getting-started/) and a quick flow
To get started, simply import [this flow](https://flows.nodered.org/flow/4b6406c9a684c26ace0430dd1826e95d) into your Node-Red instance and change the RSS feed in the "Get News RSS Feed" node to one of your choosing!
So far, the flow has been tested with BBC News and Sky News, however it should be easy to modify the flow to work with other RSS feeds if they don't work out of the box!
So far, the flow has been tested with BBC News and Sky News, however it should be easy to modify the flow to work with other RSS feeds if they dont work out of the box!

View File

@ -8,28 +8,28 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.1",
"bulma": "^0.9.1",
"@fortawesome/fontawesome-free": "^5.13.1",
"bulma": "^0.9.0",
"core-js": "^3.6.4",
"js-yaml": "^3.14.0",
"lodash.merge": "^4.6.2",
"register-service-worker": "^1.7.1",
"vue": "^2.6.12"
"vue": "^2.6.11"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.8",
"@vue/cli-plugin-eslint": "~4.5.8",
"@vue/cli-plugin-pwa": "~4.5.8",
"@vue/cli-service": "~4.5.8",
"@vue/cli-plugin-babel": "~4.4.6",
"@vue/cli-plugin-eslint": "~4.4.6",
"@vue/cli-plugin-pwa": "~4.4.6",
"@vue/cli-service": "~4.4.6",
"@vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^7.11.0",
"eslint": "^7.4.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-vue": "^7.1.0",
"prettier": "^2.1.2",
"raw-loader": "^4.0.2",
"sass": "^1.27.0",
"sass-loader": "^10.0.4",
"vue-template-compiler": "^2.6.12"
"eslint-plugin-vue": "^6.2.2",
"prettier": "^2.0.5",
"raw-loader": "^4.0.1",
"sass": "^1.26.10",
"sass-loader": "^9.0.2",
"vue-template-compiler": "^2.6.11"
}
}

View File

@ -10,7 +10,7 @@ logo: "logo.png"
header: true
footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it.
# Optional theme customization
# Optionnal theme customization
theme: default
colors:
light:
@ -42,8 +42,7 @@ colors:
message:
#url: https://b4bz.io
style: "is-dark" # See https://bulma.io/documentation/components/message/#colors for styling options.
title: "Demo !"
icon: "fa fa-grin"
title: "đź‘‹ Demo !"
content: "This is a dummy homepage demo. <br /> Find more information on <a href='https://github.com/bastienwirtz/homer'>github.com/bastienwirtz/homer</a>"
# Optional navbar
@ -52,7 +51,7 @@ links:
- name: "Contribute"
icon: "fab fa-github"
url: "https://github.com/bastienwirtz/homer"
target: "_blank" # optional html a tag target attribute
target: "_blank" # optionnal html a tag target attribute
- name: "Wiki"
icon: "fas fa-book"
url: "https://www.wikipedia.org/"
@ -69,7 +68,7 @@ services:
subtitle: "Bookmark example"
tag: "app"
url: "https://www.reddit.com/r/selfhosted/"
target: "_blank" # optional html a tag target attribute
target: "_blank" # optionnal html a tag target attribute
- name: "Another one"
logo: "assets/tools/sample2.png"
subtitle: "Another application"

View File

@ -9,7 +9,7 @@ logo: false
header: true
# Optional theme customization
# Optionnal theme customization
theme: sui
colors:
light:

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,42 +1,79 @@
{
"name": "Homer Dashboard",
"short_name": "Homer",
"name": "Dashboard",
"short_name": "homer",
"theme_color": "#3367D6",
"start_url": "../",
"icons": [
{
"src": "./icons/favicon-16x16.png",
"sizes": "16x16",
"src": "./assets/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "./icons/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "./icons/icon-any.png",
"src": "./assets/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "./assets/icons/android-chrome-maskable-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
"purpose": "maskable"
},
{
"src": "./icons/icon-any.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "any"
},
{
"src": "./icons/icon-maskable.png",
"src": "./assets/icons/android-chrome-maskable-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "./icons/safari-pinned-tab.svg",
"sizes": "any",
"type": "image/svg+xml",
"purpose": "monochrome"
"src": "./assets/icons/apple-touch-icon-60x60.png",
"sizes": "60x60",
"type": "image/png"
},
{
"src": "./assets/icons/apple-touch-icon-76x76.png",
"sizes": "76x76",
"type": "image/png"
},
{
"src": "./assets/icons/apple-touch-icon-120x120.png",
"sizes": "120x120",
"type": "image/png"
},
{
"src": "./assets/icons/apple-touch-icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "./assets/icons/apple-touch-icon-180x180.png",
"sizes": "180x180",
"type": "image/png"
},
{
"src": "./assets/icons/apple-touch-icon.png",
"sizes": "180x180",
"type": "image/png"
},
{
"src": "./assets/icons/favicon-16x16.png",
"sizes": "16x16",
"type": "image/png"
},
{
"src": "./assets/icons/favicon-32x32.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "./assets/icons/msapplication-icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "./assets/icons/mstile-150x150.png",
"sizes": "150x150",
"type": "image/png"
}
]
}

View File

@ -26,12 +26,11 @@
<Navbar
:open="showMenu"
:links="config.links"
@navbar-toggle="showMenu = !showMenu"
@navbar:toggle="showMenu = !showMenu"
>
<DarkMode :isDark="this.isDark" @updated="isDark = $event" />
<DarkMode @updated="isDark = $event" />
<LayoutToggle
:vlayout="this.vlayout"
<SettingToggle
@updated="vlayout = $event"
name="vlayout"
icon="fa-list"
@ -41,10 +40,9 @@
<SearchInput
class="navbar-item is-inline-block-mobile"
@input="filterServices"
:value="filter"
@search-focus="showMenu = true"
@search-open="navigateToFirstService"
@search-cancel="filterServices"
@search:focus="showMenu = true"
@search:open="navigateToFirstService"
@search:cancel="filterServices"
/>
</Navbar>
</div>
@ -53,7 +51,7 @@
<div v-cloak class="container">
<ConnectivityChecker
v-if="config.connectivityCheck"
@network-status-update="offline = $event"
@network:status-update="offline = $event"
/>
<div v-if="!offline">
<!-- Optional messages -->
@ -70,7 +68,6 @@
v-for="item in group.items"
:key="item.name"
v-bind:item="item"
@filter="filterTag"
:class="['column', `is-${12 / config.columns}`]"
/>
</template>
@ -93,7 +90,6 @@
<Service
v-for="item in group.items"
v-bind:item="item"
@filter="filterTag"
:key="item.url"
/>
</div>
@ -123,7 +119,7 @@ import ConnectivityChecker from "./components/ConnectivityChecker.vue";
import Service from "./components/Service.vue";
import Message from "./components/Message.vue";
import SearchInput from "./components/SearchInput.vue";
import LayoutToggle from "./components/LayoutToggle.vue";
import SettingToggle from "./components/SettingToggle.vue";
import DarkMode from "./components/DarkMode.vue";
import DynamicTheme from "./components/DynamicTheme.vue";
@ -132,24 +128,24 @@ import defaultConfig from "./assets/defaults.yml";
export default {
name: "App",
components: {
Navbar,
ConnectivityChecker,
Service,
Message,
SearchInput,
SettingToggle,
DarkMode,
DynamicTheme,
Message,
Navbar,
SearchInput,
Service,
LayoutToggle,
},
data: function () {
return {
config: null,
filter: "",
isDark: null,
offline: false,
services: null,
offline: false,
filter: "",
vlayout: true,
isDark: null,
showMenu: false,
vlayout: null,
};
},
created: async function () {
@ -163,11 +159,7 @@ export default {
}
this.config = merge(defaults, config);
this.services = this.config.services;
this.isDark = this.config.theme_use_dark;
this.vlayout = this.config.vlayout;
document.title =
this.config.documentTitle ||
`${this.config.title} | ${this.config.subtitle}`;
document.title = this.config.documentTitle || `${this.config.title} | ${this.config.subtitle}`;
if (this.config.stylesheet) {
let stylesheet = "";
for (const file of this.config.stylesheet) {
@ -216,35 +208,6 @@ export default {
console.warning("fail to open service");
}
},
filterTag: function (filter) {
this.showMenu = true;
this.$nextTick(() => {
document.getElementById("searchBox").focus();
});
this.filter = filter;
if (!filter) {
this.services = this.config.services;
return;
}
const searchResultItems = [];
for (const group of this.config.services) {
for (const item of group.items) {
if (this.matchesFilter(item)) {
searchResultItems.push(item);
}
}
}
this.services = [
{
name: filter,
icon: "fas fa-search",
items: searchResultItems,
},
];
},
filterServices: function (filter) {
this.filter = filter;

View File

@ -13,17 +13,17 @@
text-overflow: ellipsis;
}
html, body, body #app {
html {
height: 100%;
background-color: var(--background);
}
body {
font-family: "Raleway", sans-serif;
height: 100%;
#app {
height: auto;
min-height: 100%;
background-color: var(--background);
background-image: var(--background-image);
background-size: cover;
background-position: center;
@ -49,17 +49,6 @@ body {
&:hover {
background-color: var(--card-background);
}
.linkoverlay {
position:absolute;
left:0;
top:0;
bottom:0;
right:0;
}
.thirty-five {
font-size: 35px;
}
}
.message {
@ -181,7 +170,6 @@ body {
.title {
font-size: 1.1em;
line-height: 1.2em;
@include ellipsis();
}
@ -207,27 +195,17 @@ body {
}
}
}
.media-left {
pointer-events: none;
z-index: 1;
}
.media-content {
overflow: hidden;
text-overflow: inherit;
}
.infolink {
font-family: "Font Awesome 5 Free";
position: absolute;
top: 0.5rem;
right: 0.5rem;
padding: 0;
}
.tag {
color: var(--highlight-secondary);
background-color: var(--highlight-secondary);
position: absolute;
bottom: 1rem;
top: 1rem;
right: -0.2rem;
width: 3px;
overflow: hidden;

View File

@ -1,6 +1,6 @@
/*
* SUI theme
* Inspired by the great https://github.com/jeroenpardon/sui start page
* Inpired by the great https://github.com/jeroenpardon/sui start page
* Author: @bastienwirtz
*/
body #app.theme-sui {

View File

@ -2,7 +2,7 @@
<div v-if="offline" class="offline-message">
<i class="far fa-dizzy"></i>
<h1>
You're offline friend.
You're offline bro.
<span @click="checkOffline"> <i class="fas fa-redo-alt"></i></span>
</h1>
</div>
@ -44,7 +44,7 @@ export default {
that.offline = true;
})
.finally(function () {
that.$emit("network-status-update", that.offline);
that.$emit("network:status-update", that.offline);
});
},
},

View File

@ -11,23 +11,23 @@
<script>
export default {
name: "Darkmode",
props: {
isDark: Boolean,
data: function () {
return {
isDark: null,
};
},
created: function () {
let isDark =
this.isDark =
"overrideDark" in localStorage
? JSON.parse(localStorage.overrideDark)
: this.isDark === null
? matchMedia("(prefers-color-scheme: dark)").matches
: this.isDark;
this.$emit("updated", isDark);
: matchMedia("(prefers-color-scheme: dark)").matches;
this.$emit("updated", this.isDark);
},
methods: {
toggleTheme: function () {
let isDark = !this.isDark;
localStorage.overrideDark = isDark;
this.$emit("updated", isDark);
this.isDark = !this.isDark;
localStorage.overrideDark = this.isDark;
this.$emit("updated", this.isDark);
},
},
};

View File

@ -1,15 +1,17 @@
<template>
<DynamicStyle>
:root, body #app.is-light {
/* light / dark theme switch based on system pref if available */ body #app
{
{{ getVars(themes.light) }}
} @media (prefers-color-scheme: light), (prefers-color-scheme:
no-preference) { :root, body #app {
no-preference) { body #app {
{{ getVars(themes.light) }}
} } body #app.is-dark {
} } @media (prefers-color-scheme: dark) { body #app { } } /* light / dark
theme override base on user choice. */ body #app.is-dark {
{{ getVars(themes.dark) }}
} @media (prefers-color-scheme: dark) { :root, body #app {
{{ getVars(themes.dark) }}
} }
} body #app.is-light {
{{ getVars(themes.light) }}
}
</DynamicStyle>
</template>
@ -25,7 +27,7 @@ export default {
for (const themeVars in theme) {
let value = `${theme[themeVars]}`;
if (!value) {
value = "initial";
value = "inital";
} else if (themeVars == "background-image") {
value = `url(${theme[themeVars]})`;
}

View File

@ -1,43 +0,0 @@
<template>
<a v-on:click="toggleLayout()" class="navbar-item is-inline-block-mobile">
<span>
<i :class="['fas', 'fa-fw', vlayout ? icon : secondaryIcon]"></i>
</span>
<slot></slot>
</a>
</template>
<script>
export default {
name: "LayoutToggle",
props: {
name: String,
icon: String,
iconAlt: String,
vlayout: Boolean,
},
data: function () {
return {
secondaryIcon: null,
};
},
created: function () {
this.secondaryIcon = this.iconAlt || this.icon;
let vlayout;
if (this.name in localStorage) {
vlayout = JSON.parse(localStorage[this.name]);
} else {
vlayout = this.vlayout === null ? true : this.vlayout;
}
this.$emit("updated", vlayout);
},
methods: {
toggleLayout: function () {
let vlayout = !this.vlayout;
localStorage[this.name] = vlayout;
this.$emit("updated", vlayout);
},
},
};
</script>

View File

@ -1,10 +1,7 @@
<template>
<article v-if="show" class="message" :class="message.style">
<div v-if="message.title || message.icon" class="message-header">
<p>
<i v-if="message.icon" :class="`fa-fw ${message.icon}`"></i>
{{ message.title }}
</p>
<div v-if="message.title" class="message-header">
<p>{{ message.title }}</p>
</div>
<div
v-if="message.content"

View File

@ -9,7 +9,7 @@
aria-expanded="false"
class="navbar-burger"
:class="{ 'is-active': showMenu }"
v-on:click="$emit('navbar-toggle')"
v-on:click="$emit('navbar:toggle')"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
@ -21,8 +21,8 @@
<a
class="navbar-item"
rel="noreferrer"
v-for="(link, key) in links"
:key="key"
v-for="link in links"
:key="link.url"
:href="link.url"
:target="link.target"
>

View File

@ -2,13 +2,12 @@
<div class="search-bar">
<label for="search" class="search-label"></label>
<input
id="searchBox"
type="text"
ref="search"
:value="value"
@input="$emit('input', $event.target.value.toLowerCase())"
@keyup.enter.exact="$emit('search-open')"
@keyup.alt.enter="$emit('search-open', '_blank')"
@keyup.enter.exact="$emit('search:open')"
@keyup.alt.enter="$emit('search:open', '_blank')"
/>
</div>
</template>
@ -21,7 +20,7 @@ export default {
this._keyListener = function (event) {
if (event.key === "/") {
event.preventDefault();
this.$emit("search-focus");
this.$emit("search:focus");
this.$nextTick(() => {
this.$refs.search.focus();
});
@ -29,7 +28,7 @@ export default {
if (event.key === "Escape") {
this.$refs.search.value = "";
this.$refs.search.blur();
this.$emit("search-cancel");
this.$emit("search:cancel");
}
};
document.addEventListener("keydown", this._keyListener.bind(this));

View File

@ -1,26 +1,40 @@
<template>
<component v-on="$listeners" v-bind:is="component" :item="item"></component>
<div>
<div class="card" :class="item.class">
<a :href="item.url" :target="item.target" rel="noreferrer">
<div class="card-content">
<div class="media">
<div v-if="item.logo" class="media-left">
<figure class="image is-48x48">
<img :src="item.logo" :alt="`${item.name} logo`" />
</figure>
</div>
<div v-if="item.icon" class="media-left">
<figure class="image is-48x48">
<i style="font-size: 35px;" :class="['fa-fw', item.icon]"></i>
</figure>
</div>
<div class="media-content">
<p class="title is-4">{{ item.name }}</p>
<p class="subtitle is-6">{{ item.subtitle }}</p>
</div>
</div>
<div class="tag" :class="item.tagstyle" v-if="item.tag">
<strong class="tag-text">#{{ item.tag }}</strong>
</div>
</div>
</a>
</div>
</div>
</template>
<script>
import Generic from "./services/Generic.vue";
export default {
name: "Service",
components: {
Generic,
},
props: {
item: Object,
},
computed: {
component() {
const type = this.item.type || "Generic";
if (type == "Generic") {
return Generic;
}
return () => import(`./services/${type}.vue`);
},
},
};
</script>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,40 @@
<template>
<a v-on:click="toggleSetting()" class="navbar-item is-inline-block-mobile">
<span><i :class="['fas', 'fa-fw', value ? icon : iconAlt]"></i></span>
<slot></slot>
</a>
</template>
<script>
export default {
name: "SettingToggle",
props: {
name: String,
icon: String,
iconAlt: String,
},
data: function () {
return {
value: true,
};
},
created: function () {
if (!this.iconAlt) {
this.iconAlt = this.icon;
}
if (this.name in localStorage) {
this.value = JSON.parse(localStorage[this.name]);
}
this.$emit("updated", this.value);
},
methods: {
toggleSetting: function () {
this.value = !this.value;
localStorage[this.name] = this.value;
this.$emit("updated", this.value);
},
},
};
</script>

View File

@ -1,84 +0,0 @@
<script>
export default {};
</script>
<style></style>
<script>
export default {};
</script>
<style></style>
<template>
<div>
<div
class="card"
:style="`background-color:${item.background};`"
:class="item.class"
>
<a
:href="item.url"
class="linkoverlay"
:target="item.target"
rel="noreferrer"
></a>
<div class="card-content">
<div class="media">
<div v-if="item.logo" class="media-left">
<figure class="image is-48x48">
<img :src="item.logo" :alt="`${item.name} logo`" />
</figure>
</div>
<div v-if="item.icon" class="media-left">
<figure class="image is-48x48">
<i class="thirty-five" :class="['fa-fw', item.icon]"></i>
</figure>
</div>
<div class="media-content">
<p class="title is-4">{{ item.name }}</p>
<p class="subtitle is-6">{{ item.subtitle }}</p>
</div>
</div>
<a
v-if="item.info"
:href="item.info"
:target="item.infotarget"
rel="noreferrer"
>
<div class="infolink">
<i class="fas fa-info-circle"></i>
</div>
</a>
<div
v-on:click="filterTag()"
class="tag is-clickable"
:class="item.tagstyle"
v-if="item.tag"
>
<strong class="tag-text">#{{ item.tag }}</strong>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "Generic",
props: {
item: Object,
},
methods: {
filterTag: function () {
this.$emit("filter", this.item.tag.toLowerCase());
},
},
};
</script>
<style scoped lang="scss">
.media-left img {
max-height: 100%;
}
</style>

View File

@ -1,96 +0,0 @@
<template>
<div>
<div class="card" :class="item.class">
<a :href="item.url" :target="item.target" rel="noreferrer">
<div class="card-content">
<div class="media">
<div v-if="item.logo" class="media-left">
<figure class="image is-48x48">
<img :src="item.logo" :alt="`${item.name} logo`" />
</figure>
</div>
<div v-if="item.icon" class="media-left">
<figure class="image is-48x48">
<i class="thirty-five" :class="['fa-fw', item.icon]"></i>
</figure>
</div>
<div class="media-content">
<p class="title is-4">{{ item.name }}</p>
<p class="subtitle is-6">{{ item.subtitle }}</p>
</div>
<div v-if="status" class="status" :class="status.status">
{{ status.status }}
</div>
</div>
<div
v-on:click="filterTag()"
class="tag is-clickable"
:class="item.tagstyle"
v-if="item.tag"
>
<strong class="tag-text">#{{ item.tag }}</strong>
</div>
</div>
</a>
</div>
</div>
</template>
<script>
export default {
name: "PiHole",
props: {
item: Object,
},
data: () => {
return {
status: null,
};
},
created: function () {
this.fetchStatus();
},
methods: {
fetchStatus: async function () {
this.status = await fetch(`${this.item.url}/api.php`).then((response) =>
response.json()
);
},
filterTag: function () {
this.$emit("filter", this.item.tag.toLowerCase());
},
},
};
</script>
<style scoped lang="scss">
.media-left img {
max-height: 100%;
}
.status {
font-size: 0.8rem;
color: var(--text-title);
&.enabled:before {
background-color: #94e185;
border-color: #78d965;
box-shadow: 0px 0px 4px 1px #94e185;
}
&.disabled:before {
background-color: #c9404d;
border-color: #c42c3b;
box-shadow: 0px 0px 4px 1px #c9404d;
}
&:before {
content: " ";
display: inline-block;
width: 7px;
height: 7px;
margin-right: 10px;
border: 1px solid #000;
border-radius: 7px;
}
}
</style>

View File

@ -1,5 +1,3 @@
const manifestOptions = require("./public/assets/manifest.json");
module.exports = {
chainWebpack: (config) => {
config.module
@ -12,17 +10,94 @@ module.exports = {
publicPath: "",
pwa: {
manifestPath: "assets/manifest.json",
manifestOptions: {
start_url: "../",
},
appleMobileWebAppStatusBarStyle: "black",
appleMobileWebAppCapable: "yes",
name: manifestOptions.name,
themeColor: manifestOptions.theme_color,
manifestOptions,
name: "Homer Dashboard",
short_name: "Homer",
theme_color: "#3367D6",
icons: [
{
src: "./assets/icons/android-chrome-192x192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "./assets/icons/android-chrome-512x512.png",
sizes: "512x512",
type: "image/png",
},
{
src: "./assets/icons/android-chrome-maskable-192x192.png",
sizes: "192x192",
type: "image/png",
purpose: "maskable",
},
{
src: "./assets/icons/android-chrome-maskable-512x512.png",
sizes: "512x512",
type: "image/png",
purpose: "maskable",
},
{
src: "./assets/icons/apple-touch-icon-60x60.png",
sizes: "60x60",
type: "image/png",
},
{
src: "./assets/icons/apple-touch-icon-76x76.png",
sizes: "76x76",
type: "image/png",
},
{
src: "./assets/icons/apple-touch-icon-120x120.png",
sizes: "120x120",
type: "image/png",
},
{
src: "./assets/icons/apple-touch-icon-152x152.png",
sizes: "152x152",
type: "image/png",
},
{
src: "./assets/icons/apple-touch-icon-180x180.png",
sizes: "180x180",
type: "image/png",
},
{
src: "./assets/icons/apple-touch-icon.png",
sizes: "180x180",
type: "image/png",
},
{
src: "./assets/icons/favicon-16x16.png",
sizes: "16x16",
type: "image/png",
},
{
src: "./assets/icons/favicon-32x32.png",
sizes: "32x32",
type: "image/png",
},
{
src: "./assets/icons/msapplication-icon-144x144.png",
sizes: "144x144",
type: "image/png",
},
{
src: "./assets/icons/mstile-150x150.png",
sizes: "150x150",
type: "image/png",
},
],
iconPaths: {
favicon32: "assets/icons/favicon-32x32.png",
favicon16: "assets/icons/favicon-16x16.png",
appleTouchIcon: "assets/icons/icon-maskable.png",
appleTouchIcon: "assets/icons/apple-touch-icon-152x152.png",
maskIcon: "assets/icons/safari-pinned-tab.svg",
msTileImage: "assets/icons/icon-any.png",
msTileImage: "assets/icons/msapplication-icon-144x144.png",
},
},
};

2215
yarn.lock

File diff suppressed because it is too large Load Diff