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.07.2" have entirely different histories.

46 changed files with 1286 additions and 1780 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

@ -28,7 +28,7 @@ ENV UID 911
ENV PORT 8080
RUN addgroup -S ${GROUP} -g ${GID} && adduser -D -S -u ${UID} ${USER} ${GROUP} && \
apk add -U --no-cache darkhttpd su-exec && \
apk add -U darkhttpd su-exec && \
rm /usr/bin/qemu-arm-static
COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/

View File

@ -28,7 +28,7 @@ ENV UID 911
ENV PORT 8080
RUN addgroup -S ${GROUP} -g ${GID} && adduser -D -S -u ${UID} ${USER} ${GROUP} && \
apk add -U --no-cache darkhttpd su-exec && \
apk add -U darkhttpd su-exec && \
rm /usr/bin/qemu-aarch64-static
COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/

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
@ -70,41 +70,12 @@ See [documentation](docs/configuration.md) for information about the configurati
### Using docker
To launch container :
```sh
docker run -p 8080:8080 -v /your/local/assets/:/www/assets b4bz/homer:latest
```
Default assets will be automatically installed in the `/www/assets` directory. Use `UID` and/or `GID` env var to change the assets owner (`docker run -e "UID=1000" -e "GID=1000" [...]`).
### Using docker-compose
The `docker-compose.yml` file must be edited to match your needs.
Set the port and volume (equivalent to -p and -v arguments) :
```yaml
volumes:
- /your/local/assets/:/www/assets
ports:
- 8080:8080
```
To launch container :
```sh
cd /path/to/docker-compose.yml
docker-compose up -d
```
Default assets will be automatically installed in the `/www/assets` directory. Use `UID` and/or `GID` env var to change the assets owner, also in `docker-compose.yml` :
```yaml
environment:
- UID=1000
- GID=1000
```
### Using the release tarball (prebuilt, ready to use)
Download and extract the latest the latest release (`homer.zip`) from the [release page](https://github.com/bastienwirtz/homer/releases), rename the `assets/config.yml.dist` file to `assets/config.yml`, and put it behind a webserver.

View File

@ -1,14 +0,0 @@
---
version: "2"
services:
homer:
image: b4bz/homer
container_name: homer
volumes:
- /your/local/assets/:/www/assets
ports:
- 8080:8080
#environment:
# - UID=1000
# - GID=1000
restart: unless-stopped

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
---
@ -13,8 +13,7 @@ Title, icons, links, colors, and services can be configured in the `config.yml`
title: "App dashboard"
subtitle: "Homer"
# documentTitle: "Welcome" # Customize the browser tab text
logo: "assets/logo.png"
logo: "assets/homer.png"
# Alternatively a fa icon can be provided:
# icon: "fas fa-skull-crossbones"
@ -22,17 +21,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.
# stylesheet:
# - "assets/custom.css"
# Here is the exaustive list of customization parameters
# However all value are optional and will fallback to default if not set.
@ -50,7 +42,6 @@ colors:
text-subtitle: "#424242"
card-shadow: rgba(0, 0, 0, 0.1)
link-hover: "#363636"
background-image: "assets/your/light/bg.png"
dark:
highlight-primary: "#3367d6"
highlight-secondary: "#4285f4"
@ -63,14 +54,12 @@ colors:
text-subtitle: "#f5f5f5"
card-shadow: rgba(0, 0, 0, 0.4)
link-hover: "#ffdd57"
background-image: "assets/your/dark/bg.png"
# Optional message
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 +78,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 +88,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 +98,12 @@ 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`
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
url: "https://www.reddit.com/r/selfhosted/"
target: "_blank" # optionnal html a tag target attribute
```
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

@ -5,9 +5,9 @@ Here is a collection of neat tips and tricks that Homer users have come up with!
## Use Homer as a custom "new tab" page
#### `by @vosdev`
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.
This [extension](https://addons.mozilla.org/firefox/addon/custom-new-tab-page) allows 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 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:

View File

@ -1,8 +0,0 @@
@charset "UTF-8";
/* Custom card colors */
/* Use with `class:` property of services in config.yml */
body #app .card.green {
background-color: #006600;
color: #00ff00;
}

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

@ -14,7 +14,7 @@
<div v-cloak class="container">
<div class="logo">
<img v-if="config.logo" :src="config.logo" alt="dashboard logo" />
<i v-if="config.icon" :class="config.icon"></i>
<i v-if="config.icon" :class="['fa-fw', config.icon]"></i>
</div>
<div class="dashboard-title">
<span class="headline">{{ config.subtitle }}</span>
@ -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,60 +128,38 @@ 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 () {
const defaults = jsyaml.load(defaultConfig);
let config;
try {
config = await this.getConfig();
} catch (error) {
console.log(error);
config = this.handleErrors("⚠️ Error loading configuration", error);
}
let config = await this.getConfig();
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}`;
if (this.config.stylesheet) {
let stylesheet = "";
for (const file of this.config.stylesheet) {
stylesheet += `@import "${file}";`;
}
this.createStylesheet(stylesheet);
}
document.title = `${this.config.title} | ${this.config.subtitle}`;
},
methods: {
getConfig: function (path = "assets/config.yml") {
return fetch(path).then((response) => {
if (response.redirected) {
// This allows to work with authentication proxies.
window.location.href = response.url;
return;
}
if (!response.ok) {
throw Error(`${response.statusText}: ${response.body}`);
throw Error(response.statusText);
}
const that = this;
@ -199,6 +173,9 @@ export default {
return that.getConfig(config.externalConfig);
}
return config;
})
.catch((error) => {
return this.handleErrors("⚠️ Error loading configuration", error);
});
});
},
@ -216,35 +193,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;
@ -279,11 +227,6 @@ export default {
},
};
},
createStylesheet: function (css) {
let style = document.createElement("style");
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
},
},
};
</script>

View File

@ -13,20 +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-image: var(--background-image);
background-size: cover;
background-position: center;
background-color: var(--background);
color: var(--text);
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
@ -49,17 +46,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 +167,6 @@ body {
.title {
font-size: 1.1em;
line-height: 1.2em;
@include ellipsis();
}
@ -207,27 +192,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

@ -24,7 +24,6 @@ colors:
text-subtitle: "#424242"
card-shadow: rgba(0, 0, 0, 0.1)
link-hover: "#363636"
background-image: ""
dark:
highlight-primary: "#3367d6"
highlight-secondary: "#4285f4"
@ -37,7 +36,6 @@ colors:
text-subtitle: "#f5f5f5"
card-shadow: rgba(0, 0, 0, 0.4)
link-hover: "#ffdd57"
background-image: ""
message: ~
links: []

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>
@ -23,13 +25,7 @@ export default {
getVars: function (theme) {
let vars = [];
for (const themeVars in theme) {
let value = `${theme[themeVars]}`;
if (!value) {
value = "initial";
} else if (themeVars == "background-image") {
value = `url(${theme[themeVars]})`;
}
vars.push(`--${themeVars}: ${value}`);
vars.push(`--${themeVars}: ${theme[themeVars]}`);
}
return vars.join(";");
},

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,14 +21,15 @@
<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"
>
<i
v-if="link.icon"
:class="['fa-fw', link.icon, { 'mr-2': link.name }]"
style="margin-right: 6px;"
:class="['fa-fw', link.icon]"
></i>
{{ link.name }}
</a>

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">
<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