mirror of https://github.com/bastienwirtz/homer
Merge branch 'main' into linkding_support
# Conflicts: # docs/customservices.mdpull/895/head
commit
2024297f61
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
|
@ -0,0 +1,359 @@
|
||||||
|
{
|
||||||
|
"$id": "https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json",
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema",
|
||||||
|
"description": "https://github.com/bastienwirtz/homer/blob/main/docs/configuration.md",
|
||||||
|
"examples": [],
|
||||||
|
"title": "Homer Dashboard configuration",
|
||||||
|
"type": "object",
|
||||||
|
"definitions": {
|
||||||
|
"Colors": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"light": {
|
||||||
|
"$ref": "#/definitions/ColorSet"
|
||||||
|
},
|
||||||
|
"dark": {
|
||||||
|
"$ref": "#/definitions/ColorSet"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Colors"
|
||||||
|
},
|
||||||
|
"ColorSet": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"highlight-primary": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"highlight-secondary": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"highlight-hover": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"card-background": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"text-header": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"text-title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"text-subtitle": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"card-shadow": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"link-hover": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"background-image": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Defaults": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"layout": {
|
||||||
|
"enum": [
|
||||||
|
"columns",
|
||||||
|
"list"
|
||||||
|
],
|
||||||
|
"description": "Layout of the dashboard, either 'columns' or 'list'"
|
||||||
|
},
|
||||||
|
"colorTheme": {
|
||||||
|
"enum": [
|
||||||
|
"auto",
|
||||||
|
"light",
|
||||||
|
"dark"
|
||||||
|
],
|
||||||
|
"description": "One of 'auto', 'light', or 'dark'"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Defaults"
|
||||||
|
},
|
||||||
|
"Hotkey": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"search": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "hotkey for search, e.g. Shift"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"search"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Link": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name as seen in the navbar"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Fontawesome icon"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Url of the link. When #filename is used, it is a link to another homer page, while 'filename' is the name of the config file"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "html tag target attribute like _blank for a new page"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"title": "Link"
|
||||||
|
},
|
||||||
|
"Message": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri"
|
||||||
|
},
|
||||||
|
"mapping": {
|
||||||
|
"$ref": "#/definitions/Mapping",
|
||||||
|
"description": "Mapping for the content loaded from the URL"
|
||||||
|
},
|
||||||
|
"refreshInterval": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "The refresh interval in milliseconds for reloading the message url"
|
||||||
|
},
|
||||||
|
"style": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "See https://bulma.io/documentation/components/message/#colors for styling options"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Title of the message box"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Fontawesome icon for the message box"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "HTML content for the message box"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Messagebox"
|
||||||
|
},
|
||||||
|
"Mapping": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Mapping"
|
||||||
|
},
|
||||||
|
"Proxy": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"useCredentials": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "# send cookies & authorization headers when fetching service specific data. Set to `true` if you use an authentication proxy. Can be overrided on service level. "
|
||||||
|
},
|
||||||
|
"headers": {
|
||||||
|
"$ref": "#/definitions/Headers",
|
||||||
|
"description": "send custom headers when fetching service specific data. Can also be set on a service level."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Proxy"
|
||||||
|
},
|
||||||
|
"Headers": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"title": "Headers"
|
||||||
|
},
|
||||||
|
"Service": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Service name"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Fontawesome icon for the service"
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "A path to an image can also be provided. Note that icon take precedence if both icon and logo are set."
|
||||||
|
},
|
||||||
|
"class": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional css class to add on the service group. Example 'highlight-purple'"
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Item"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"items"
|
||||||
|
],
|
||||||
|
"title": "Service"
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to a logo. Alternatively a fa icon can be provided"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Fontawesome icon for the item, alternative for logo"
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Show tag"
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional keyword used for searching purpose"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Url of this item"
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "html tag target attribute like _blank for a new page"
|
||||||
|
},
|
||||||
|
"tagstyle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Styleclass for the tag"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Optional, loads a specific component that provides extra features. MUST MATCH a file name (without file extension) available in `src/components/services`"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Item"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"externalConfig": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Use external configuration file. Using this will ignore remaining config in this file externalConfig: https://example.com/server-luci/config.yaml"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Title of the dashboard"
|
||||||
|
},
|
||||||
|
"subtitle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Subtitle of the dashboard"
|
||||||
|
},
|
||||||
|
"documentTitle": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Title of the document. When not filled, title (and subtitle will be used)"
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to logo image"
|
||||||
|
},
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Dashboard icon"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Show header, default is true"
|
||||||
|
},
|
||||||
|
"hotkey": {
|
||||||
|
"$ref": "#/definitions/Hotkey",
|
||||||
|
"description": "Define hotkeys, for example for search"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "footer Line content. HTML is supported. Set false if you want to hide it."
|
||||||
|
},
|
||||||
|
"columns": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "'auto' or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)",
|
||||||
|
"format": "integer"
|
||||||
|
},
|
||||||
|
"connectivityCheck": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "# whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example). You should set it to true when using an authentication proxy, it also reloads the page when a redirection is detected when checking connectivity."
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"$ref": "#/definitions/Proxy",
|
||||||
|
"description": "Optional: Proxy / hosting option"
|
||||||
|
},
|
||||||
|
"defaults": {
|
||||||
|
"$ref": "#/definitions/Defaults"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "'default' or one of the themes available in 'src/assets/themes'"
|
||||||
|
},
|
||||||
|
"stylesheet": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Will load custom CSS files. Especially useful for custom icon sets. Entries are paths to the stylesheets"
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"$ref": "#/definitions/Colors"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"$ref": "#/definitions/Message",
|
||||||
|
"description": "Messagebox"
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"description": "Links in the navigation bar",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Link"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"description": "Services",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/Service"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ RUN pnpm build
|
||||||
# production stage
|
# production stage
|
||||||
FROM alpine:3.21
|
FROM alpine:3.21
|
||||||
|
|
||||||
|
ARG VERSION_TAG=latest
|
||||||
|
|
||||||
LABEL \
|
LABEL \
|
||||||
org.label-schema.schema-version="1.0" \
|
org.label-schema.schema-version="1.0" \
|
||||||
org.label-schema.version="$VERSION_TAG" \
|
org.label-schema.version="$VERSION_TAG" \
|
||||||
|
|
|
@ -26,6 +26,7 @@ within Homer:
|
||||||
- [Jellystat](#jellystat)
|
- [Jellystat](#jellystat)
|
||||||
- [Lidarr, Prowlarr, Sonarr, Readarr and Radarr](#lidarr-prowlarr-sonarr-readarr-and-radarr)
|
- [Lidarr, Prowlarr, Sonarr, Readarr and Radarr](#lidarr-prowlarr-sonarr-readarr-and-radarr)
|
||||||
- [Linkding](#linkding)
|
- [Linkding](#linkding)
|
||||||
|
- [Matrix](#matrix)
|
||||||
- [Mealie](#mealie)
|
- [Mealie](#mealie)
|
||||||
- [Medusa](#medusa)
|
- [Medusa](#medusa)
|
||||||
- [Nextcloud](#nextcloud)
|
- [Nextcloud](#nextcloud)
|
||||||
|
@ -49,7 +50,9 @@ within Homer:
|
||||||
- [Tautulli](#tautulli)
|
- [Tautulli](#tautulli)
|
||||||
- [Tdarr](#tdarr)
|
- [Tdarr](#tdarr)
|
||||||
- [Traefik](#traefik)
|
- [Traefik](#traefik)
|
||||||
|
- [TrueNas Scale](#truenas-scale)
|
||||||
- [Uptime Kuma](#uptime-kuma)
|
- [Uptime Kuma](#uptime-kuma)
|
||||||
|
- [Vaultwarden](#vaultwarden)
|
||||||
- [Wallabag](#wallabag)
|
- [Wallabag](#wallabag)
|
||||||
- [What's Up Docker](#whats-up-docker)
|
- [What's Up Docker](#whats-up-docker)
|
||||||
|
|
||||||
|
@ -298,6 +301,18 @@ This integration supports at max 15 results from Linkding. But you can add it mu
|
||||||
query: "#ToDo #Homer"
|
query: "#ToDo #Homer"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Matrix
|
||||||
|
|
||||||
|
This service displays a version string instead of a subtitle. The indicator
|
||||||
|
shows if Matrix Server is online, offline
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: "Matrix - Server"
|
||||||
|
type: "Matrix"
|
||||||
|
logo: "assets/tools/sample.png"
|
||||||
|
url: "http://matrix.example.com"
|
||||||
|
```
|
||||||
|
|
||||||
## Mealie
|
## Mealie
|
||||||
|
|
||||||
First off make sure to remove an existing `subtitle` as it will take precedence if set.
|
First off make sure to remove an existing `subtitle` as it will take precedence if set.
|
||||||
|
@ -470,6 +485,9 @@ This service displays info about the total number of containers managed by your
|
||||||
In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass
|
In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass
|
||||||
it to the apikey field.
|
it to the apikey field.
|
||||||
By default, every connected environments will be checked. To select specific ones, add an "environments" entry which can be a simple string or an array containing all the selected environments name.
|
By default, every connected environments will be checked. To select specific ones, add an "environments" entry which can be a simple string or an array containing all the selected environments name.
|
||||||
|
### New features:
|
||||||
|
Displays the Portainer version from /api/status
|
||||||
|
Shows online/offline status depending on API reachability
|
||||||
|
|
||||||
See <https://docs.portainer.io/api/access#creating-an-access-token>
|
See <https://docs.portainer.io/api/access#creating-an-access-token>
|
||||||
|
|
||||||
|
@ -662,6 +680,18 @@ This service displays a version string instead of a subtitle. Example configurat
|
||||||
url: http://traefik.example.com
|
url: http://traefik.example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Truenas Scale
|
||||||
|
|
||||||
|
This service displays a version string instead of a subtitle. Example configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: "Truenas"
|
||||||
|
type: "TruenasScale"
|
||||||
|
logo: "assets/tools/sample.png"
|
||||||
|
url: "http://truenas.example.com"
|
||||||
|
api_token: "your_api_token"
|
||||||
|
```
|
||||||
|
|
||||||
## Uptime Kuma
|
## Uptime Kuma
|
||||||
|
|
||||||
Using the Uptime Kuma service you can display info about your instance uptime right on your Homer dashboard.
|
Using the Uptime Kuma service you can display info about your instance uptime right on your Homer dashboard.
|
||||||
|
@ -676,6 +706,17 @@ The following configuration is available for the UptimeKuma service. Needs v1.13
|
||||||
slug: "myCustomDashboard" # Defaults to "default" if not provided.
|
slug: "myCustomDashboard" # Defaults to "default" if not provided.
|
||||||
type: "UptimeKuma"
|
type: "UptimeKuma"
|
||||||
```
|
```
|
||||||
|
## Vaultwarden
|
||||||
|
|
||||||
|
This service displays a version string instead of a subtitle. The indicator
|
||||||
|
shows if Vaultwarden is online, offline
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: "Vaultwarden - Server"
|
||||||
|
type: "Vaultwarden"
|
||||||
|
logo: "assets/tools/sample.png"
|
||||||
|
url: "http://vaultwarden.example.com"
|
||||||
|
```
|
||||||
|
|
||||||
## Wallabag
|
## Wallabag
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,21 @@ Then when Homer reads your config, it will substitute your anchors automatically
|
||||||
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 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!
|
||||||
|
|
||||||
|
## YAML auto complete with a YAML schema
|
||||||
|
|
||||||
|
A lot of editor support auto completion, see <https://www.schemastore.org/json/>
|
||||||
|
The homer schema is available here: <https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json>
|
||||||
|
|
||||||
|
For example with IntelliJ you can define:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# $schema: https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json
|
||||||
|
```
|
||||||
|
With VSCode you can define it like this:
|
||||||
|
```yaml
|
||||||
|
# yaml-language-server: $schema=https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json
|
||||||
|
```
|
||||||
|
|
||||||
## Remotely edit your config with Code Server
|
## Remotely edit your config with Code Server
|
||||||
|
|
||||||
#### `by @JamiePhonic`
|
#### `by @JamiePhonic`
|
||||||
|
|
|
@ -14,4 +14,5 @@ if [[ "${INIT_ASSETS}" == "1" ]] && [[ ! -f "/www/assets/config.yml" ]]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Starting webserver"
|
echo "Starting webserver"
|
||||||
|
exec 3>&1
|
||||||
exec lighttpd -D -f /lighttpd.conf
|
exec lighttpd -D -f /lighttpd.conf
|
||||||
|
|
|
@ -18,6 +18,6 @@ export default [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ignores: ["*.d.ts", "**/coverage", "**/dist"],
|
ignores: ["**/dist/**", "**/dist-ssr/**", "**/coverage/**"],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -2,7 +2,7 @@ include "/etc/lighttpd/mime-types.conf"
|
||||||
include_shell "/etc/lighttpd/ipv6.sh"
|
include_shell "/etc/lighttpd/ipv6.sh"
|
||||||
|
|
||||||
server.port = env.PORT
|
server.port = env.PORT
|
||||||
server.modules = ( "mod_alias" )
|
server.modules = ( "mod_alias", "mod_accesslog" )
|
||||||
server.username = "lighttpd"
|
server.username = "lighttpd"
|
||||||
server.groupname = "lighttpd"
|
server.groupname = "lighttpd"
|
||||||
server.document-root = "/www"
|
server.document-root = "/www"
|
||||||
|
@ -10,3 +10,9 @@ alias.url = ( env.SUBFOLDER => "/www" )
|
||||||
server.indexfiles = ("index.html")
|
server.indexfiles = ("index.html")
|
||||||
server.follow-symlink = "enable"
|
server.follow-symlink = "enable"
|
||||||
server.feature-flags += ( "server.clock-jump-restart" => 0 )
|
server.feature-flags += ( "server.clock-jump-restart" => 0 )
|
||||||
|
server.max-request-field-size = 65535
|
||||||
|
accesslog.filename = "/dev/fd/3"
|
||||||
|
|
||||||
|
# Avoid logging docker healthcheck request
|
||||||
|
$HTTP["remote-ip"] == "127.0.0.1" { accesslog.filename = "" }
|
||||||
|
$HTTP["remote-ip"] == "[::1]" { accesslog.filename = "" }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "homer",
|
"name": "homer",
|
||||||
"version": "25.02.2",
|
"version": "25.03.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"prettier": "^3.5.2",
|
"prettier": "^3.5.2",
|
||||||
"sass-embedded": "^1.85.0",
|
"sass-embedded": "^1.85.0",
|
||||||
"vite": "^6.1.1",
|
"vite": "^6.1.2",
|
||||||
"vite-plugin-pwa": "^0.21.1"
|
"vite-plugin-pwa": "^0.21.1"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
989
pnpm-lock.yaml
989
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -171,9 +171,9 @@
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
line-height: 1.2em;
|
line-height: 1.3em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 3px;
|
||||||
@include ellipsis();
|
@include ellipsis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,7 @@
|
||||||
:href="link.url"
|
:href="link.url"
|
||||||
:target="link.target"
|
:target="link.target"
|
||||||
>
|
>
|
||||||
<i
|
<i v-if="link.icon" :class="['fa-fw', link.icon]"></i>
|
||||||
v-if="link.icon"
|
|
||||||
:class="['fa-fw', link.icon, { 'mr-2': link.name }]"
|
|
||||||
></i>
|
|
||||||
{{ link.name }}
|
{{ link.name }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,3 +62,11 @@ export default {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@media (min-width: 1023px) {
|
||||||
|
i.fa-fw {
|
||||||
|
width: 0.8em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
hasFocus: function () {
|
hasFocus: function () {
|
||||||
return document.activeElement == this.$refs.search
|
return document.activeElement == this.$refs.search;
|
||||||
},
|
},
|
||||||
setSearchURL: function (value) {
|
setSearchURL: function (value) {
|
||||||
const url = new URL(window.location);
|
const url = new URL(window.location);
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<component :is="component" :item="item" :proxy="proxy"></component>
|
<Generic v-if="isGeneric" :item="item"></Generic>
|
||||||
|
<component :is="component" v-else :item="item" :proxy="proxy"></component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineAsyncComponent } from "vue";
|
import { defineAsyncComponent } from "vue";
|
||||||
import Generic from "./services/Generic.vue";
|
|
||||||
|
const defaultService = "Generic";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Service",
|
name: "Service",
|
||||||
|
@ -13,12 +15,13 @@ export default {
|
||||||
proxy: Object,
|
proxy: Object,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isGeneric() {
|
||||||
|
return defaultService === (this.item.type || defaultService);
|
||||||
|
},
|
||||||
component() {
|
component() {
|
||||||
const type = this.item.type || "Generic";
|
return defineAsyncComponent(
|
||||||
if (type === "Generic") {
|
() => import(`./services/${this.item.type}.vue`),
|
||||||
return Generic;
|
);
|
||||||
}
|
|
||||||
return defineAsyncComponent(() => import(`./services/${type}.vue`));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "AdGuardHome",
|
name: "AdGuardHome",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -15,13 +15,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "CopyToClipboard",
|
name: "CopyToClipboard",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Docuseal",
|
name: "Docuseal",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Emby",
|
name: "Emby",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -25,13 +25,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FreshRSS",
|
name: "FreshRSS",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Gitea",
|
name: "Gitea",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -17,13 +17,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Glances",
|
name: "Glances",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -19,13 +19,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Gotify",
|
name: "Gotify",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -18,13 +18,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Healthchecks",
|
name: "Healthchecks",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "HomeAssistant",
|
name: "HomeAssistant",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -27,13 +27,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Immich",
|
name: "Immich",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -20,13 +20,9 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Jellyfin",
|
name: "Jellyfin",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -27,13 +27,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Lidarr",
|
name: "Lidarr",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<Generic :item="item">
|
||||||
|
<template #content>
|
||||||
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
|
<p class="subtitle is-6">
|
||||||
|
<template v-if="item.subtitle">
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="versionstring">
|
||||||
|
Version {{ versionstring }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #indicator>
|
||||||
|
<div v-if="status" class="status" :class="status">
|
||||||
|
{{ status }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Generic>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Matrix",
|
||||||
|
mixins: [service],
|
||||||
|
props: {
|
||||||
|
item: Object,
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
fetchOk: null,
|
||||||
|
versionstring: null,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
status: function () {
|
||||||
|
return this.fetchOk ? "online" : "offline";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchStatus();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchStatus: async function () {
|
||||||
|
this.fetch("_matrix/federation/v1/version")
|
||||||
|
.then((response) => {
|
||||||
|
this.fetchOk = true;
|
||||||
|
this.versionstring = response.server.version;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.fetchOk = false;
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.status {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
|
||||||
|
&.online:before {
|
||||||
|
background-color: #94e185;
|
||||||
|
border-color: #78d965;
|
||||||
|
box-shadow: 0 0 5px 1px #94e185;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline:before {
|
||||||
|
background-color: #c9404d;
|
||||||
|
border-color: #c42c3b;
|
||||||
|
box-shadow: 0 0 5px 1px #c9404d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -19,13 +19,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Mealie",
|
name: "Mealie",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -33,13 +33,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Medusa",
|
name: "Medusa",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -22,13 +22,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Mylar",
|
name: "Mylar",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -16,13 +16,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Nextcloud",
|
name: "Nextcloud",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -51,13 +51,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "OctoPrint",
|
name: "OctoPrint",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Olivetin",
|
name: "Olivetin",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "OpenHAB",
|
name: "OpenHAB",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -16,13 +16,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Paperless",
|
name: "Paperless",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -19,13 +19,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PeaNUT",
|
name: "PeaNUT",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -27,13 +27,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PiAlert",
|
name: "PiAlert",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PiHole",
|
name: "PiHole",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Ping",
|
name: "Ping",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<Generic :item="item">
|
<Generic :item="item">
|
||||||
|
<template #content>
|
||||||
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
|
<p class="subtitle is-6">
|
||||||
|
<template v-if="item.subtitle">
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="versionstring">
|
||||||
|
Version {{ versionstring }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
<template #indicator>
|
<template #indicator>
|
||||||
<div class="notifs">
|
<div class="notifs">
|
||||||
<strong v-if="running > 0" class="notif running" title="Running">
|
<strong v-if="running > 0" class="notif running" title="Running">
|
||||||
|
@ -16,19 +27,18 @@
|
||||||
{{ misc }}
|
{{ misc }}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="status" class="status" :class="status">
|
||||||
|
{{ status }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Generic>
|
</Generic>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Portainer",
|
name: "Portainer",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
@ -36,6 +46,8 @@ export default {
|
||||||
data: () => ({
|
data: () => ({
|
||||||
endpoints: null,
|
endpoints: null,
|
||||||
containers: null,
|
containers: null,
|
||||||
|
fetchOk: null,
|
||||||
|
versionstring: null,
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
running: function () {
|
running: function () {
|
||||||
|
@ -65,9 +77,13 @@ export default {
|
||||||
);
|
);
|
||||||
}).length;
|
}).length;
|
||||||
},
|
},
|
||||||
|
status: function () {
|
||||||
|
return this.fetchOk ? "online" : "offline";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetchStatus();
|
this.fetchStatus();
|
||||||
|
this.fetchVersion();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchStatus: async function () {
|
fetchStatus: async function () {
|
||||||
|
@ -103,11 +119,54 @@ export default {
|
||||||
|
|
||||||
this.containers = containers;
|
this.containers = containers;
|
||||||
},
|
},
|
||||||
|
fetchVersion: async function () {
|
||||||
|
const headers = {
|
||||||
|
"X-Api-Key": this.item.apikey,
|
||||||
|
};
|
||||||
|
this.fetch("/api/status", { headers })
|
||||||
|
.then((response) => {
|
||||||
|
this.fetchOk = true;
|
||||||
|
this.versionstring = response.Version;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.fetchOk = false;
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
.status {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
|
||||||
|
&.online:before {
|
||||||
|
background-color: #94e185;
|
||||||
|
border-color: #78d965;
|
||||||
|
box-shadow: 0 0 5px 1px #94e185;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline:before {
|
||||||
|
background-color: #c9404d;
|
||||||
|
border-color: #c42c3b;
|
||||||
|
box-shadow: 0 0 5px 1px #c9404d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notifs {
|
.notifs {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: white;
|
color: white;
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
const AlertsStatus = Object.freeze({
|
const AlertsStatus = Object.freeze({
|
||||||
firing: "firing",
|
firing: "firing",
|
||||||
|
@ -29,9 +28,6 @@ const AlertsStatus = Object.freeze({
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Prometheus",
|
name: "Prometheus",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -22,13 +22,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Prowlarr",
|
name: "Prowlarr",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -76,13 +76,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Proxmox",
|
name: "Proxmox",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -27,16 +27,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
const V3_API = "/api/v3";
|
const V3_API = "/api/v3";
|
||||||
const LEGACY_API = "/api";
|
const LEGACY_API = "/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Radarr",
|
name: "Radarr",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -27,15 +27,11 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
const API = "/api/v1";
|
const API = "/api/v1";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Readarr",
|
name: "Readarr",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -23,8 +23,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
// Units to add to download and upload rates.
|
// Units to add to download and upload rates.
|
||||||
const units = ["B", "kiB", "MiB", "GiB"];
|
const units = ["B", "kiB", "MiB", "GiB"];
|
||||||
|
|
||||||
|
@ -48,7 +46,6 @@ const displayRate = (rate) => {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "RTorrent",
|
name: "RTorrent",
|
||||||
components: { Generic },
|
|
||||||
props: { item: Object },
|
props: { item: Object },
|
||||||
// Properties for download, upload, torrent count and errors.
|
// Properties for download, upload, torrent count and errors.
|
||||||
data: () => ({ dl: null, ul: null, count: null, error: null }),
|
data: () => ({ dl: null, ul: null, count: null, error: null }),
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SABnzbd",
|
name: "SABnzbd",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -24,13 +24,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Scrutiny",
|
name: "Scrutiny",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -28,16 +28,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
const V3_API = "/api/v3";
|
const V3_API = "/api/v3";
|
||||||
const LEGACY_API = "/api";
|
const LEGACY_API = "/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Sonarr",
|
name: "Sonarr",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -15,13 +15,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "SpeedtestTracker",
|
name: "SpeedtestTracker",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Tautulli",
|
name: "Tautulli",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -28,13 +28,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Tdarr",
|
name: "Tdarr",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -20,16 +20,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
let currentTheme;
|
let currentTheme;
|
||||||
const app = document.getElementById("app");
|
const app = document.getElementById("app");
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ThemeChooser",
|
name: "ThemeChooser",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Traefik",
|
name: "Traefik",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
<template>
|
||||||
|
<Generic :item="item">
|
||||||
|
<template #content>
|
||||||
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
|
<p class="subtitle is-6">
|
||||||
|
<template v-if="item.subtitle">
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="versionstring">
|
||||||
|
<span class="is-hidden-touch">Version {{ versionstring }}</span>
|
||||||
|
<span class="is-hidden-desktop"
|
||||||
|
>Version {{ versionstring.split("-").pop() }}</span
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #indicator>
|
||||||
|
<div v-if="status" class="status" :class="status">
|
||||||
|
{{ status }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Generic>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "TruenasScale",
|
||||||
|
mixins: [service],
|
||||||
|
props: {
|
||||||
|
item: Object,
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
fetchOk: null,
|
||||||
|
versionstring: null,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
status: function () {
|
||||||
|
return this.fetchOk ? "online" : "offline";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchStatus();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchStatus: async function () {
|
||||||
|
let headers = {};
|
||||||
|
if (this.item.api_token) {
|
||||||
|
headers["Authorization"] = `Bearer ${this.item.api_token}`;
|
||||||
|
}
|
||||||
|
this.fetch("/api/v2.0/system/version", { headers })
|
||||||
|
.then((response) => {
|
||||||
|
this.fetchOk = true;
|
||||||
|
this.versionstring = response;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.fetchOk = false;
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.status {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
|
||||||
|
&.online:before {
|
||||||
|
background-color: #94e185;
|
||||||
|
border-color: #78d965;
|
||||||
|
box-shadow: 0 0 5px 1px #94e185;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline:before {
|
||||||
|
background-color: #c9404d;
|
||||||
|
border-color: #c42c3b;
|
||||||
|
box-shadow: 0 0 5px 1px #c9404d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "UptimeKuma",
|
name: "UptimeKuma",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<template>
|
||||||
|
<Generic :item="item">
|
||||||
|
<template #content>
|
||||||
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
|
<p class="subtitle is-6">
|
||||||
|
<template v-if="item.subtitle">
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="versionstring">
|
||||||
|
Version {{ versionstring }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #indicator>
|
||||||
|
<div v-if="status" class="status" :class="status">
|
||||||
|
{{ status }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Generic>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Vaultwarden",
|
||||||
|
mixins: [service],
|
||||||
|
props: {
|
||||||
|
item: Object,
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
fetchOk: null,
|
||||||
|
versionstring: null,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
status: function () {
|
||||||
|
return this.fetchOk ? "online" : "offline";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchStatus();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchStatus: async function () {
|
||||||
|
this.fetch("api/version")
|
||||||
|
.then((response) => {
|
||||||
|
this.fetchOk = true;
|
||||||
|
this.versionstring = response;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
this.fetchOk = false;
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.status {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
white-space: nowrap;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
|
||||||
|
&.online:before {
|
||||||
|
background-color: #94e185;
|
||||||
|
border-color: #78d965;
|
||||||
|
box-shadow: 0 0 5px 1px #94e185;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline:before {
|
||||||
|
background-color: #c9404d;
|
||||||
|
border-color: #c42c3b;
|
||||||
|
box-shadow: 0 0 5px 1px #c9404d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -22,13 +22,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "WUD",
|
name: "WUD",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -21,13 +21,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Wallabag",
|
name: "Wallabag",
|
||||||
components: {
|
|
||||||
Generic,
|
|
||||||
},
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
|
|
|
@ -28,7 +28,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import service from "@/mixins/service.js";
|
import service from "@/mixins/service.js";
|
||||||
import Generic from "./Generic.vue";
|
|
||||||
const units = ["B", "KB", "MB", "GB"];
|
const units = ["B", "KB", "MB", "GB"];
|
||||||
|
|
||||||
// Take the rate in bytes and keep dividing it by 1k until the lowest
|
// Take the rate in bytes and keep dividing it by 1k until the lowest
|
||||||
|
@ -50,7 +49,6 @@ const displayRate = (rate) => {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "QBittorrent",
|
name: "QBittorrent",
|
||||||
components: { Generic },
|
|
||||||
mixins: [service],
|
mixins: [service],
|
||||||
props: { item: Object },
|
props: { item: Object },
|
||||||
data: () => ({ dl: null, ul: null, count: null, error: null }),
|
data: () => ({ dl: null, ul: null, count: null, error: null }),
|
||||||
|
|
|
@ -3,9 +3,12 @@ import { createApp, h } from "vue";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
import Generic from "./components/services/Generic.vue";
|
||||||
|
|
||||||
app.component("DynamicStyle", (_props, context) => {
|
app
|
||||||
return h("style", {}, context.slots);
|
.component("Generic", Generic)
|
||||||
});
|
.component("DynamicStyle", (_props, context) => {
|
||||||
|
return h("style", {}, context.slots);
|
||||||
|
});
|
||||||
|
|
||||||
app.mount("#app-mount");
|
app.mount("#app-mount");
|
||||||
|
|
Loading…
Reference in New Issue