Compare commits
46 Commits
Author | SHA1 | Date | |
---|---|---|---|
707fc0e7ba | |||
|
f8d0761d0c | ||
|
c701075dfa | ||
6297655a34 | |||
e207e58f6a | |||
af9037991b | |||
0b8ea779f3 | |||
c30533045a | |||
|
ff0b5150f1 | ||
|
5a85322c92 | ||
553076011b | |||
837446bc33 | |||
8ada6cca70 | |||
e028716b18 | |||
7bab0dbdd9 | |||
4a31e1e146 | |||
1f12c43da8 | |||
188bcb4f21 | |||
6b17544aa0 | |||
ae8d62958b | |||
729d1b5309 | |||
1fc60ffe4f | |||
2662b17049 | |||
|
d1b29caaa6 | ||
|
e75945851c | ||
|
13071ae3d1 | ||
|
5b727eee02 | ||
|
37dfd2a132 | ||
|
9a14de007e | ||
|
ed8b17e0df | ||
|
c368290e32 | ||
|
00b069fc6f | ||
|
40d3e8de76 | ||
|
1017cc9864 | ||
|
b04e718367 | ||
|
ab8136bab7 | ||
488e4bbe7f | |||
62ec6fa099 | |||
|
67fd101a38 | ||
|
0e045b4c55 | ||
|
e608701404 | ||
|
2b5f88db18 | ||
|
b8c81389dc | ||
|
5b05842512 | ||
|
80f6a13140 | ||
|
f70fc3ecae |
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -12,7 +12,7 @@ Fixes # (issue)
|
|||||||
|
|
||||||
## Checklist:
|
## Checklist:
|
||||||
|
|
||||||
- [ ] I read & comply with the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/master/CONTRIBUTING.md)
|
- [ ] I've 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 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 have made corresponding changes the documentation (README.md).
|
||||||
- [ ] I've check my modifications for any breaking change, especially in the `config.yml` file
|
- [ ] I've checked my modifications for any breaking changes, especially in the `config.yml` file
|
||||||
|
@ -6,10 +6,10 @@ First off, thank you for considering contributing to Homer!
|
|||||||
|
|
||||||
### Project philosophy
|
### Project philosophy
|
||||||
|
|
||||||
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
|
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
|
||||||
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/.
|
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 versionning 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 versioning 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.
|
- 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
|
### Roadmap
|
||||||
@ -21,7 +21,7 @@ Feel free to open an issue if you have any question.
|
|||||||
|
|
||||||
### Code of conduct and guidelines
|
### 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 recomandation, 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 recommendation, it is mandatory.
|
||||||
|
|
||||||
For all contributions, please respect the following guidelines:
|
For all contributions, please respect the following guidelines:
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<img
|
<img
|
||||||
width="180"
|
width="180"
|
||||||
alt="Homer's donut"
|
alt="Homer's donut"
|
||||||
src="https://raw.githubusercontent.com//bastienwirtz/homer/master/public/logo.png">
|
src="https://raw.githubusercontent.com//bastienwirtz/homer/main/public/logo.png">
|
||||||
<br/>
|
<br/>
|
||||||
Homer
|
Homer
|
||||||
</h1>
|
</h1>
|
||||||
@ -36,7 +36,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://raw.github.com/bastienwirtz/homer/master/docs/screenshot.png" width="100%">
|
<img src="https://raw.github.com/bastienwirtz/homer/main/docs/screenshot.png" width="100%">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
@ -45,7 +45,7 @@
|
|||||||
- [Configuration](docs/configuration.md)
|
- [Configuration](docs/configuration.md)
|
||||||
- [Tips & tricks](docs/tips-and-tricks.md)
|
- [Tips & tricks](docs/tips-and-tricks.md)
|
||||||
- [Roadmap](#roadmap)
|
- [Roadmap](#roadmap)
|
||||||
- [Developement](docs/developement.md)
|
- [Development](docs/development.md)
|
||||||
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
## Configuration
|
## 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 developement 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 development mode), using [yaml](http://yaml.org/) format.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
@ -22,10 +22,12 @@ 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.
|
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)
|
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)
|
connectivityCheck: true # whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example)
|
||||||
|
|
||||||
# Optional theming
|
# Optional theming
|
||||||
theme: default # 'default' or one of the theme available in 'src/assets/themes'.
|
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
|
# Optional custom stylesheet
|
||||||
# Will load custom CSS files. Especially useful for custom icon sets.
|
# Will load custom CSS files. Especially useful for custom icon sets.
|
||||||
@ -68,6 +70,7 @@ message:
|
|||||||
# url: "https://<my-api-endpoint>" # Can fetch information from an endpoint to override value below.
|
# url: "https://<my-api-endpoint>" # Can fetch information from an endpoint to override value below.
|
||||||
style: "is-warning"
|
style: "is-warning"
|
||||||
title: "Optional message!"
|
title: "Optional message!"
|
||||||
|
icon: "fa fa-exclamation-triangle"
|
||||||
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
|
||||||
# Optional navbar
|
# Optional navbar
|
||||||
@ -86,7 +89,7 @@ links:
|
|||||||
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
|
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
|
||||||
services:
|
services:
|
||||||
- name: "Application"
|
- name: "Application"
|
||||||
icon: "fa fa-code-fork"
|
icon: "fas fa-code-branch"
|
||||||
items:
|
items:
|
||||||
- name: "Awesome app"
|
- name: "Awesome app"
|
||||||
logo: "assets/tools/sample.png"
|
logo: "assets/tools/sample.png"
|
||||||
@ -96,6 +99,8 @@ services:
|
|||||||
tag: "app"
|
tag: "app"
|
||||||
url: "https://www.reddit.com/r/selfhosted/"
|
url: "https://www.reddit.com/r/selfhosted/"
|
||||||
target: "_blank" # optional html tag target attribute
|
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"
|
- name: "Another one"
|
||||||
logo: "assets/tools/sample2.png"
|
logo: "assets/tools/sample2.png"
|
||||||
subtitle: "Another application"
|
subtitle: "Another application"
|
||||||
@ -106,13 +111,15 @@ services:
|
|||||||
- name: "Other group"
|
- name: "Other group"
|
||||||
icon: "fas fa-heartbeat"
|
icon: "fas fa-heartbeat"
|
||||||
items:
|
items:
|
||||||
- name: "Another app"
|
- name: "Pi-hole"
|
||||||
logo: "assets/tools/sample.png"
|
logo: "assets/tools/sample.png"
|
||||||
subtitle: "Another example"
|
subtitle: "Network-wide Ad Blocking"
|
||||||
tag: "other"
|
tag: "other"
|
||||||
url: "https://www.reddit.com/r/selfhosted/"
|
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
|
target: "_blank" # optional html a tag target attribute
|
||||||
# class: "green" # optional custom CSS class for card, useful with custom stylesheet
|
# 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:
|
If you choose to fetch message information from an endpoint, the output format should be:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
## Developement
|
## Development
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Using yarn (recommended)
|
# Using yarn (recommended)
|
||||||
@ -13,7 +13,7 @@ npm run serve
|
|||||||
### Themes
|
### Themes
|
||||||
|
|
||||||
Themes are meant to be simple customization (written in [scss](https://sass-lang.com/documentation/syntax)).
|
Themes are meant to be simple customization (written in [scss](https://sass-lang.com/documentation/syntax)).
|
||||||
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.
|
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.
|
||||||
|
|
||||||
```scss
|
```scss
|
||||||
// `src/assets/themes/my-awesome-theme.scss`
|
// `src/assets/themes/my-awesome-theme.scss`
|
@ -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.
|
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
|
```yaml
|
||||||
- name: "Reddit"
|
- name: "Reddit"
|
||||||
@ -24,7 +24,7 @@ The firefox extension loads Homer in an iframe on your new tab page, meaning you
|
|||||||
## YAML Anchors
|
## YAML Anchors
|
||||||
#### `by @JamiePhonic`
|
#### `by @JamiePhonic`
|
||||||
|
|
||||||
Since Homer is configured using YAML, it supports all of YAML's helpful fetaures, such as anchoring!
|
Since Homer is configured using YAML, it supports all of YAML's helpful features, such as anchoring!
|
||||||
|
|
||||||
For example, you can define tags and tag styles for each "item" in a service.
|
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)
|
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
|
target: "_blank" # optional html tag target attribute
|
||||||
```
|
```
|
||||||
|
|
||||||
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!
|
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!
|
||||||
Great if you have a lot of services or a lot of tags!
|
Great if you have a lot of services or a lot of tags!
|
||||||
|
|
||||||
## Remotely edit your config with Code Server
|
## Remotely edit your config with Code Server
|
||||||
#### `by @JamiePhonic`
|
#### `by @JamiePhonic`
|
||||||
|
|
||||||
Homer doesn't yet provide a way to edit your configuration from inside Homer itself, but that doesnt mean it cant be done!
|
Homer doesn't yet provide a way to edit your configuration from inside Homer itself, but that doesn't 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!
|
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!
|
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 dont 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 don't work out of the box!
|
||||||
|
28
package.json
@ -8,28 +8,28 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.13.1",
|
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||||
"bulma": "^0.9.0",
|
"bulma": "^0.9.1",
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.4",
|
||||||
"js-yaml": "^3.14.0",
|
"js-yaml": "^3.14.0",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"register-service-worker": "^1.7.1",
|
"register-service-worker": "^1.7.1",
|
||||||
"vue": "^2.6.11"
|
"vue": "^2.6.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.4.6",
|
"@vue/cli-plugin-babel": "~4.5.8",
|
||||||
"@vue/cli-plugin-eslint": "~4.4.6",
|
"@vue/cli-plugin-eslint": "~4.5.8",
|
||||||
"@vue/cli-plugin-pwa": "~4.4.6",
|
"@vue/cli-plugin-pwa": "~4.5.8",
|
||||||
"@vue/cli-service": "~4.4.6",
|
"@vue/cli-service": "~4.5.8",
|
||||||
"@vue/eslint-config-prettier": "^6.0.0",
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^7.4.0",
|
"eslint": "^7.11.0",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^7.1.0",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.1.2",
|
||||||
"raw-loader": "^4.0.1",
|
"raw-loader": "^4.0.2",
|
||||||
"sass": "^1.26.10",
|
"sass": "^1.27.0",
|
||||||
"sass-loader": "^9.0.2",
|
"sass-loader": "^10.0.4",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.12"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ logo: "logo.png"
|
|||||||
header: true
|
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.
|
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.
|
||||||
|
|
||||||
# Optionnal theme customization
|
# Optional theme customization
|
||||||
theme: default
|
theme: default
|
||||||
colors:
|
colors:
|
||||||
light:
|
light:
|
||||||
@ -42,7 +42,8 @@ colors:
|
|||||||
message:
|
message:
|
||||||
#url: https://b4bz.io
|
#url: https://b4bz.io
|
||||||
style: "is-dark" # See https://bulma.io/documentation/components/message/#colors for styling options.
|
style: "is-dark" # See https://bulma.io/documentation/components/message/#colors for styling options.
|
||||||
title: "đź‘‹ Demo !"
|
title: "Demo !"
|
||||||
|
icon: "fa fa-grin"
|
||||||
content: "This is a dummy homepage demo. <br /> Find more information on <a href='https://github.com/bastienwirtz/homer'>github.com/bastienwirtz/homer</a>"
|
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
|
# Optional navbar
|
||||||
@ -51,7 +52,7 @@ links:
|
|||||||
- name: "Contribute"
|
- name: "Contribute"
|
||||||
icon: "fab fa-github"
|
icon: "fab fa-github"
|
||||||
url: "https://github.com/bastienwirtz/homer"
|
url: "https://github.com/bastienwirtz/homer"
|
||||||
target: "_blank" # optionnal html a tag target attribute
|
target: "_blank" # optional html a tag target attribute
|
||||||
- name: "Wiki"
|
- name: "Wiki"
|
||||||
icon: "fas fa-book"
|
icon: "fas fa-book"
|
||||||
url: "https://www.wikipedia.org/"
|
url: "https://www.wikipedia.org/"
|
||||||
@ -68,7 +69,7 @@ services:
|
|||||||
subtitle: "Bookmark example"
|
subtitle: "Bookmark example"
|
||||||
tag: "app"
|
tag: "app"
|
||||||
url: "https://www.reddit.com/r/selfhosted/"
|
url: "https://www.reddit.com/r/selfhosted/"
|
||||||
target: "_blank" # optionnal html a tag target attribute
|
target: "_blank" # optional html a tag target attribute
|
||||||
- name: "Another one"
|
- name: "Another one"
|
||||||
logo: "assets/tools/sample2.png"
|
logo: "assets/tools/sample2.png"
|
||||||
subtitle: "Another application"
|
subtitle: "Another application"
|
||||||
|
@ -9,7 +9,7 @@ logo: false
|
|||||||
|
|
||||||
header: true
|
header: true
|
||||||
|
|
||||||
# Optionnal theme customization
|
# Optional theme customization
|
||||||
theme: sui
|
theme: sui
|
||||||
colors:
|
colors:
|
||||||
light:
|
light:
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 24 KiB |
BIN
public/assets/icons/icon-any.png
Normal file
After Width: | Height: | Size: 75 KiB |
1
public/assets/icons/icon-any.svg
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
public/assets/icons/icon-maskable.png
Normal file
After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 19 KiB |
@ -1,79 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "Dashboard",
|
"name": "Homer Dashboard",
|
||||||
"short_name": "homer",
|
"short_name": "Homer",
|
||||||
"theme_color": "#3367D6",
|
"theme_color": "#3367D6",
|
||||||
|
"start_url": "../",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "./assets/icons/android-chrome-192x192.png",
|
"src": "./icons/favicon-16x16.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",
|
"sizes": "16x16",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "./assets/icons/favicon-32x32.png",
|
"src": "./icons/favicon-32x32.png",
|
||||||
"sizes": "32x32",
|
"sizes": "32x32",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "./assets/icons/msapplication-icon-144x144.png",
|
"src": "./icons/icon-any.png",
|
||||||
"sizes": "144x144",
|
"sizes": "512x512",
|
||||||
"type": "image/png"
|
"type": "image/png",
|
||||||
|
"purpose": "any"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "./assets/icons/mstile-150x150.png",
|
"src": "./icons/icon-any.svg",
|
||||||
"sizes": "150x150",
|
"sizes": "any",
|
||||||
"type": "image/png"
|
"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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
71
src/App.vue
@ -26,11 +26,12 @@
|
|||||||
<Navbar
|
<Navbar
|
||||||
:open="showMenu"
|
:open="showMenu"
|
||||||
:links="config.links"
|
:links="config.links"
|
||||||
@navbar:toggle="showMenu = !showMenu"
|
@navbar-toggle="showMenu = !showMenu"
|
||||||
>
|
>
|
||||||
<DarkMode @updated="isDark = $event" />
|
<DarkMode :isDark="this.isDark" @updated="isDark = $event" />
|
||||||
|
|
||||||
<SettingToggle
|
<LayoutToggle
|
||||||
|
:vlayout="this.vlayout"
|
||||||
@updated="vlayout = $event"
|
@updated="vlayout = $event"
|
||||||
name="vlayout"
|
name="vlayout"
|
||||||
icon="fa-list"
|
icon="fa-list"
|
||||||
@ -40,9 +41,10 @@
|
|||||||
<SearchInput
|
<SearchInput
|
||||||
class="navbar-item is-inline-block-mobile"
|
class="navbar-item is-inline-block-mobile"
|
||||||
@input="filterServices"
|
@input="filterServices"
|
||||||
@search:focus="showMenu = true"
|
:value="filter"
|
||||||
@search:open="navigateToFirstService"
|
@search-focus="showMenu = true"
|
||||||
@search:cancel="filterServices"
|
@search-open="navigateToFirstService"
|
||||||
|
@search-cancel="filterServices"
|
||||||
/>
|
/>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
</div>
|
</div>
|
||||||
@ -51,7 +53,7 @@
|
|||||||
<div v-cloak class="container">
|
<div v-cloak class="container">
|
||||||
<ConnectivityChecker
|
<ConnectivityChecker
|
||||||
v-if="config.connectivityCheck"
|
v-if="config.connectivityCheck"
|
||||||
@network:status-update="offline = $event"
|
@network-status-update="offline = $event"
|
||||||
/>
|
/>
|
||||||
<div v-if="!offline">
|
<div v-if="!offline">
|
||||||
<!-- Optional messages -->
|
<!-- Optional messages -->
|
||||||
@ -68,6 +70,7 @@
|
|||||||
v-for="item in group.items"
|
v-for="item in group.items"
|
||||||
:key="item.name"
|
:key="item.name"
|
||||||
v-bind:item="item"
|
v-bind:item="item"
|
||||||
|
@filter="filterTag"
|
||||||
:class="['column', `is-${12 / config.columns}`]"
|
:class="['column', `is-${12 / config.columns}`]"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -90,6 +93,7 @@
|
|||||||
<Service
|
<Service
|
||||||
v-for="item in group.items"
|
v-for="item in group.items"
|
||||||
v-bind:item="item"
|
v-bind:item="item"
|
||||||
|
@filter="filterTag"
|
||||||
:key="item.url"
|
:key="item.url"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -119,7 +123,7 @@ import ConnectivityChecker from "./components/ConnectivityChecker.vue";
|
|||||||
import Service from "./components/Service.vue";
|
import Service from "./components/Service.vue";
|
||||||
import Message from "./components/Message.vue";
|
import Message from "./components/Message.vue";
|
||||||
import SearchInput from "./components/SearchInput.vue";
|
import SearchInput from "./components/SearchInput.vue";
|
||||||
import SettingToggle from "./components/SettingToggle.vue";
|
import LayoutToggle from "./components/LayoutToggle.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";
|
||||||
|
|
||||||
@ -128,24 +132,24 @@ import defaultConfig from "./assets/defaults.yml";
|
|||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
Navbar,
|
|
||||||
ConnectivityChecker,
|
ConnectivityChecker,
|
||||||
Service,
|
|
||||||
Message,
|
|
||||||
SearchInput,
|
|
||||||
SettingToggle,
|
|
||||||
DarkMode,
|
DarkMode,
|
||||||
DynamicTheme,
|
DynamicTheme,
|
||||||
|
Message,
|
||||||
|
Navbar,
|
||||||
|
SearchInput,
|
||||||
|
Service,
|
||||||
|
LayoutToggle,
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
config: null,
|
config: null,
|
||||||
services: null,
|
|
||||||
offline: false,
|
|
||||||
filter: "",
|
filter: "",
|
||||||
vlayout: true,
|
|
||||||
isDark: null,
|
isDark: null,
|
||||||
|
offline: false,
|
||||||
|
services: null,
|
||||||
showMenu: false,
|
showMenu: false,
|
||||||
|
vlayout: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created: async function () {
|
created: async function () {
|
||||||
@ -159,7 +163,11 @@ export default {
|
|||||||
}
|
}
|
||||||
this.config = merge(defaults, config);
|
this.config = merge(defaults, config);
|
||||||
this.services = this.config.services;
|
this.services = this.config.services;
|
||||||
document.title = this.config.documentTitle || `${this.config.title} | ${this.config.subtitle}`;
|
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) {
|
if (this.config.stylesheet) {
|
||||||
let stylesheet = "";
|
let stylesheet = "";
|
||||||
for (const file of this.config.stylesheet) {
|
for (const file of this.config.stylesheet) {
|
||||||
@ -208,6 +216,35 @@ export default {
|
|||||||
console.warning("fail to open service");
|
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) {
|
filterServices: function (filter) {
|
||||||
this.filter = filter;
|
this.filter = filter;
|
||||||
|
|
||||||
|
@ -13,17 +13,17 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html, body, body #app {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background-color: var(--background);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Raleway", sans-serif;
|
font-family: "Raleway", sans-serif;
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
height: auto;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
background-color: var(--background);
|
|
||||||
background-image: var(--background-image);
|
background-image: var(--background-image);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@ -49,6 +49,17 @@ body {
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--card-background);
|
background-color: var(--card-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.linkoverlay {
|
||||||
|
position:absolute;
|
||||||
|
left:0;
|
||||||
|
top:0;
|
||||||
|
bottom:0;
|
||||||
|
right:0;
|
||||||
|
}
|
||||||
|
.thirty-five {
|
||||||
|
font-size: 35px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
@ -170,6 +181,7 @@ body {
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
line-height: 1.2em;
|
||||||
@include ellipsis();
|
@include ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,17 +207,27 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.media-left {
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
.media-content {
|
.media-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: inherit;
|
text-overflow: inherit;
|
||||||
}
|
}
|
||||||
|
.infolink {
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5rem;
|
||||||
|
right: 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.tag {
|
.tag {
|
||||||
color: var(--highlight-secondary);
|
color: var(--highlight-secondary);
|
||||||
background-color: var(--highlight-secondary);
|
background-color: var(--highlight-secondary);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1rem;
|
bottom: 1rem;
|
||||||
right: -0.2rem;
|
right: -0.2rem;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* SUI theme
|
* SUI theme
|
||||||
* Inpired by the great https://github.com/jeroenpardon/sui start page
|
* Inspired by the great https://github.com/jeroenpardon/sui start page
|
||||||
* Author: @bastienwirtz
|
* Author: @bastienwirtz
|
||||||
*/
|
*/
|
||||||
body #app.theme-sui {
|
body #app.theme-sui {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div v-if="offline" class="offline-message">
|
<div v-if="offline" class="offline-message">
|
||||||
<i class="far fa-dizzy"></i>
|
<i class="far fa-dizzy"></i>
|
||||||
<h1>
|
<h1>
|
||||||
You're offline bro.
|
You're offline friend.
|
||||||
<span @click="checkOffline"> <i class="fas fa-redo-alt"></i></span>
|
<span @click="checkOffline"> <i class="fas fa-redo-alt"></i></span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@ -44,7 +44,7 @@ export default {
|
|||||||
that.offline = true;
|
that.offline = true;
|
||||||
})
|
})
|
||||||
.finally(function () {
|
.finally(function () {
|
||||||
that.$emit("network:status-update", that.offline);
|
that.$emit("network-status-update", that.offline);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -11,23 +11,23 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "Darkmode",
|
name: "Darkmode",
|
||||||
data: function () {
|
props: {
|
||||||
return {
|
isDark: Boolean,
|
||||||
isDark: null,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
created: function () {
|
created: function () {
|
||||||
this.isDark =
|
let isDark =
|
||||||
"overrideDark" in localStorage
|
"overrideDark" in localStorage
|
||||||
? JSON.parse(localStorage.overrideDark)
|
? JSON.parse(localStorage.overrideDark)
|
||||||
: matchMedia("(prefers-color-scheme: dark)").matches;
|
: this.isDark === null
|
||||||
this.$emit("updated", this.isDark);
|
? matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
|
: this.isDark;
|
||||||
|
this.$emit("updated", isDark);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleTheme: function () {
|
toggleTheme: function () {
|
||||||
this.isDark = !this.isDark;
|
let isDark = !this.isDark;
|
||||||
localStorage.overrideDark = this.isDark;
|
localStorage.overrideDark = isDark;
|
||||||
this.$emit("updated", this.isDark);
|
this.$emit("updated", isDark);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<DynamicStyle>
|
<DynamicStyle>
|
||||||
/* light / dark theme switch based on system pref if available */ body #app
|
:root, body #app.is-light {
|
||||||
{
|
|
||||||
{{ getVars(themes.light) }}
|
{{ getVars(themes.light) }}
|
||||||
} @media (prefers-color-scheme: light), (prefers-color-scheme:
|
} @media (prefers-color-scheme: light), (prefers-color-scheme:
|
||||||
no-preference) { body #app {
|
no-preference) { :root, body #app {
|
||||||
{{ getVars(themes.light) }}
|
{{ getVars(themes.light) }}
|
||||||
} } @media (prefers-color-scheme: dark) { body #app { } } /* light / dark
|
} } body #app.is-dark {
|
||||||
theme override base on user choice. */ body #app.is-dark {
|
|
||||||
{{ getVars(themes.dark) }}
|
{{ getVars(themes.dark) }}
|
||||||
} body #app.is-light {
|
} @media (prefers-color-scheme: dark) { :root, body #app {
|
||||||
{{ getVars(themes.light) }}
|
{{ getVars(themes.dark) }}
|
||||||
}
|
} }
|
||||||
</DynamicStyle>
|
</DynamicStyle>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -27,7 +25,7 @@ export default {
|
|||||||
for (const themeVars in theme) {
|
for (const themeVars in theme) {
|
||||||
let value = `${theme[themeVars]}`;
|
let value = `${theme[themeVars]}`;
|
||||||
if (!value) {
|
if (!value) {
|
||||||
value = "inital";
|
value = "initial";
|
||||||
} else if (themeVars == "background-image") {
|
} else if (themeVars == "background-image") {
|
||||||
value = `url(${theme[themeVars]})`;
|
value = `url(${theme[themeVars]})`;
|
||||||
}
|
}
|
||||||
|
43
src/components/LayoutToggle.vue
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<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>
|
@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<article v-if="show" class="message" :class="message.style">
|
<article v-if="show" class="message" :class="message.style">
|
||||||
<div v-if="message.title" class="message-header">
|
<div v-if="message.title || message.icon" class="message-header">
|
||||||
<p>{{ message.title }}</p>
|
<p>
|
||||||
|
<i v-if="message.icon" :class="`fa-fw ${message.icon}`"></i>
|
||||||
|
{{ message.title }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="message.content"
|
v-if="message.content"
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
class="navbar-burger"
|
class="navbar-burger"
|
||||||
:class="{ 'is-active': showMenu }"
|
: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>
|
||||||
<span aria-hidden="true"></span>
|
<span aria-hidden="true"></span>
|
||||||
@ -21,8 +21,8 @@
|
|||||||
<a
|
<a
|
||||||
class="navbar-item"
|
class="navbar-item"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
v-for="link in links"
|
v-for="(link, key) in links"
|
||||||
:key="link.url"
|
:key="key"
|
||||||
:href="link.url"
|
:href="link.url"
|
||||||
:target="link.target"
|
:target="link.target"
|
||||||
>
|
>
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
<div class="search-bar">
|
<div class="search-bar">
|
||||||
<label for="search" class="search-label"></label>
|
<label for="search" class="search-label"></label>
|
||||||
<input
|
<input
|
||||||
|
id="searchBox"
|
||||||
type="text"
|
type="text"
|
||||||
ref="search"
|
ref="search"
|
||||||
:value="value"
|
:value="value"
|
||||||
@input="$emit('input', $event.target.value.toLowerCase())"
|
@input="$emit('input', $event.target.value.toLowerCase())"
|
||||||
@keyup.enter.exact="$emit('search:open')"
|
@keyup.enter.exact="$emit('search-open')"
|
||||||
@keyup.alt.enter="$emit('search:open', '_blank')"
|
@keyup.alt.enter="$emit('search-open', '_blank')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -20,7 +21,7 @@ export default {
|
|||||||
this._keyListener = function (event) {
|
this._keyListener = function (event) {
|
||||||
if (event.key === "/") {
|
if (event.key === "/") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.$emit("search:focus");
|
this.$emit("search-focus");
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$refs.search.focus();
|
this.$refs.search.focus();
|
||||||
});
|
});
|
||||||
@ -28,7 +29,7 @@ export default {
|
|||||||
if (event.key === "Escape") {
|
if (event.key === "Escape") {
|
||||||
this.$refs.search.value = "";
|
this.$refs.search.value = "";
|
||||||
this.$refs.search.blur();
|
this.$refs.search.blur();
|
||||||
this.$emit("search:cancel");
|
this.$emit("search-cancel");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
document.addEventListener("keydown", this._keyListener.bind(this));
|
document.addEventListener("keydown", this._keyListener.bind(this));
|
||||||
|
@ -1,40 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<component v-on="$listeners" v-bind:is="component" :item="item"></component>
|
||||||
<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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import Generic from "./services/Generic.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Service",
|
name: "Service",
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
component() {
|
||||||
|
const type = this.item.type || "Generic";
|
||||||
|
if (type == "Generic") {
|
||||||
|
return Generic;
|
||||||
|
}
|
||||||
|
return () => import(`./services/${type}.vue`);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
<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>
|
|
84
src/components/services/Generic.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<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>
|
96
src/components/services/PiHole.vue
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<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>
|
@ -1,3 +1,5 @@
|
|||||||
|
const manifestOptions = require("./public/assets/manifest.json");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
chainWebpack: (config) => {
|
chainWebpack: (config) => {
|
||||||
config.module
|
config.module
|
||||||
@ -10,94 +12,17 @@ module.exports = {
|
|||||||
publicPath: "",
|
publicPath: "",
|
||||||
pwa: {
|
pwa: {
|
||||||
manifestPath: "assets/manifest.json",
|
manifestPath: "assets/manifest.json",
|
||||||
manifestOptions: {
|
|
||||||
start_url: "../",
|
|
||||||
},
|
|
||||||
appleMobileWebAppStatusBarStyle: "black",
|
appleMobileWebAppStatusBarStyle: "black",
|
||||||
appleMobileWebAppCapable: "yes",
|
appleMobileWebAppCapable: "yes",
|
||||||
name: "Homer Dashboard",
|
name: manifestOptions.name,
|
||||||
short_name: "Homer",
|
themeColor: manifestOptions.theme_color,
|
||||||
theme_color: "#3367D6",
|
manifestOptions,
|
||||||
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: {
|
iconPaths: {
|
||||||
favicon32: "assets/icons/favicon-32x32.png",
|
favicon32: "assets/icons/favicon-32x32.png",
|
||||||
favicon16: "assets/icons/favicon-16x16.png",
|
favicon16: "assets/icons/favicon-16x16.png",
|
||||||
appleTouchIcon: "assets/icons/apple-touch-icon-152x152.png",
|
appleTouchIcon: "assets/icons/icon-maskable.png",
|
||||||
maskIcon: "assets/icons/safari-pinned-tab.svg",
|
maskIcon: "assets/icons/safari-pinned-tab.svg",
|
||||||
msTileImage: "assets/icons/msapplication-icon-144x144.png",
|
msTileImage: "assets/icons/icon-any.png",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|