14 Commits
Author SHA1 Message Date
sco 4144f28a3e idk 2021-12-15 01:25:28 +00:00
sco 7adf56aea1 dev ws 2021-12-09 20:27:55 +01:00
sco 628346b035 css 2021-11-11 21:28:35 +01:00
sco fb4f86be43 wiwi 2021-11-11 02:39:48 +01:00
sco 7a7da0610b pippopipip 2021-11-10 02:26:44 +01:00
sco 3e0fdc4b5d ok 2021-11-07 18:25:46 +01:00
sco 5d554d96c4 mannaggia a dio 2021-11-07 17:29:12 +01:00
sco 06f3dd23fe bidibi 2021-11-07 17:28:57 +01:00
sco ec124a8779 3 pere 2020-11-07 18:57:10 +01:00
sco e3275f2569 diocane 2020-11-07 18:42:53 +01:00
sco 2d8ce372fb blog config 2020-11-07 18:41:51 +01:00
sco 4aa0ee4827 theming 2020-11-07 18:38:48 +01:00
sco 252d031ae6 config.yaml 2020-11-07 17:08:42 +01:00
sco 7bcfcbaec5 ai 2020-11-07 16:54:48 +01:00
11 changed files with 3888 additions and 3541 deletions
+46 -3
View File
@@ -6,9 +6,52 @@
`npm run build` frontend static build `npm run build` frontend static build
`NODE_PORT=xxxx node /path/to/node.js` otherwise won't work; it listens on 127.0.0.80
----- -----
# config.yaml # config.yaml
```---
# Homepage configuration
# See https://fontawesome.com/icons for icons options
title: "Forbidden Fruit dashboard"
subtitle: "TANGIE"
icon: "fas fa-cannabis"
# icon: "fas fa-skull-crossbones" # Optional icon
header: true
footer: false
[...]
# Services
# First level array represent a group.
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
services:
- name: "Applications"
icon: "fas fa-cloud"
items:
- name: "Unifi Controller"
subtitle: "192.168.10.11"
icon: "fas fa-server"
url: "https://unifi-controller.lan"
target: "_blank" # optionnal html a tag target attribute
- name: "Network"
icon: "fas fa-network-wired"
items:
- name: "Unifi Gateway"
icon: "fas fa-ethernet"
subtitle: "192.168.10.1"
url: "http://unifi-gateway.lan"
target: "_blank"
external:
- name: "System"
icon: "fas fa-heartbeat"
items:
- name: "System Status"
icon: "fas fa-joint"
subtitle: ""
url: "/api/sysStatus"
```
+3663 -3399
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -13,7 +13,8 @@
"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"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "~4.4.1", "@vue/cli-plugin-babel": "~4.4.1",
+51 -89
View File
@@ -2,34 +2,33 @@
# Homepage configuration # Homepage configuration
# See https://fontawesome.com/icons for icons options # See https://fontawesome.com/icons for icons options
title: "Forbidden Fruit dashboard" title: "Snakes dashboard"
subtitle: "TANGIE" subtitle: "TANGIE"
icon: "fas fa-cannabis" icon: "fas fa-cannabis"
# icon: "fas fa-skull-crossbones" # Optional icon
header: true header: true
footer: false footer: false
#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: "4" columns: "4"
# Optionnal theme customization # Optionnal theme customization
theme: default theme: default
colors: colors:
light: light:
highlight-primary: "#3367d6" highlight-primary: "#f18409"
highlight-secondary: "#4285f4" highlight-secondary: "#3a2a2f"
highlight-hover: "#5a95f5" highlight-hover: "#f18409"
background: "#f5f5f5" background: "#F3E7E2"
card-background: "#ffffff" card-background: "#E7D0C5"
text: "#363636" text: "#363636"
text-header: "#ffffff" text-header: "#ffffff"
text-title: "#303030" text-title: "#303030"
text-subtitle: "#424242" text-subtitle: "#424242"
card-shadow: rgba(0, 0, 0, 0.1) card-shadow: rgba(0, 0, 0, 0.1)
link-hover: "#363636" link-hover: "#363636"
link-color: "#571962"
dark: dark:
highlight-primary: "#3367d6" highlight-primary: "#571962"
highlight-secondary: "#4285f4" highlight-secondary: "#3a2a2f"
highlight-hover: "#5a95f5" highlight-hover: "#571962"
background: "#131313" background: "#131313"
card-background: "#2b2b2b" card-background: "#2b2b2b"
text: "#eaeaea" text: "#eaeaea"
@@ -38,24 +37,21 @@ colors:
text-subtitle: "#f5f5f5" text-subtitle: "#f5f5f5"
card-shadow: rgba(0, 0, 0, 0.4) card-shadow: rgba(0, 0, 0, 0.4)
link-hover: "#ffdd57" link-hover: "#ffdd57"
link-color: "#f18409"
# Optional message # Optional message
#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 !"
#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."
# Optional navbar # Optional navbar
# links: [] # Allows for navbar (dark mode, layout, and search) without any links # links: [] # Allows for navbar (dark mode, layout, and search) without any links
#links: links:
# - name: "Contribute" - name: "Pages"
# icon: "fab fa-github" icon: "fas fa-list"
# url: "https://github.com/bastienwirtz/homer" url: "/pages"
# target: "_blank" # optionnal html a tag target attribute
# - name: "Wiki"
# icon: "fas fa-book"
# url: "https://www.wikipedia.org/"
# Services # Services
# First level array represent a group. # First level array represent a group.
@@ -65,94 +61,60 @@ services:
icon: "fas fa-cloud" icon: "fas fa-cloud"
items: items:
- name: "Unifi Controller" - name: "Unifi Controller"
subtitle: "192.168.10.11" subtitle: "controller.snakes.wtf"
icon: "fas fa-server" icon: "fas fa-wifi"
url: "https://unifi-controller.lan" url: "https://controller.snakes.wtf"
target: "_blank" # optionnal html a tag target attribute target: "_blank" # optionnal html a tag target attribute
- name: "Pihole"
icon: "fab fa-raspberry-pi"
subtitle: "192.168.10.12"
url: "http://pihole.lan/admin"
target: "_blank"
- name: "TvHeadEnd" - name: "TvHeadEnd"
icon: "fas fa-tv" icon: "fas fa-tv"
subtitle: "192.168.10.13" subtitle: "tv.snakes.wtf"
url: "http://tvheadend.lan" url: "https://tv.snakes.wtf"
target: "_blank"
- name: "Blog"
icon: "fas fa-blog"
subtitle: "blog.snakes.wtf"
url: "https://blog.snakes.wtf"
target: "_blank"
- name: "Proxmox VE"
icon: "fas fa-server"
subtitle: "pve.snakes.wtf"
url: "https://pve.snakes.wtf:8006"
target: "_blank"
- name: "Snakes' Stream"
icon: "fas fa-play"
subtitle: "stream.snakes.wtf"
url: "https://stream.snakes.wtf"
target: "_blank" target: "_blank"
- name: "SnakesHub" - name: "SnakesHub"
icon: "fab fa-git" icon: "fab fa-git"
subtitle: "dev.jagernet.ga" subtitle: "dev.snakes.wtf"
url: "https://dev.jagernet.ga" url: "https://dev.snakes.wtf"
target: "_blank" target: "_blank"
- name: "Network" - name: "Network"
icon: "fas fa-network-wired" icon: "fas fa-network-wired"
items: items:
- name: "Unifi Gateway" - name: "OPNsense"
icon: "fas fa-ethernet" icon: "fas fa-server"
subtitle: "192.168.10.1" subtitle: "192.168.10.1"
url: "http://unifi-gateway.lan" url: "https://192.168.10.1"
target: "_blank" target: "_blank"
- name: "Bedroom Switch" - name: "Dlink DGS-1100-08"
icon: "fas fa-ethernet" icon: "fas fa-ethernet"
subtitle: "192.168.10.2" subtitle: "192.168.10.2"
url: "http://192.168.10.2" url: "http://192.168.10.2"
target: "_blank" target: "_blank"
- name: "Main Switch"
icon: "fas fa-ethernet"
subtitle: "192.168.10.3"
url: "http://192.168.10.3"
target: "_blank"
# - name: " FWR8102"
# icon: "fas fa-ethernet"
# subtitle: "192.168.10.5"
# url: "http://192.168.10.5"
# target: "_blank"
- name: "FRITZ!Box Fon 5140"
icon: "fas fa-ethernet"
subtitle: "192.168.10.6"
url: "http://192.168.10.6"
target: "_blank"
- name: " Turn on PC"
icon: "fas fa-ethernet"
subtitle: "192.168.10.15"
url: "/api/powerOnPc"
target: "_blank"
external: external:
- name: "System" - name: "System"
icon: "fas fa-heartbeat" icon: "fas fa-heartbeat"
items: items:
- name: "System Status" #- name: ""
icon: "fas fa-joint" # icon: ""
subtitle: "" # subtitle: " http://192.168.44.5:3000 "
url: "/api/sysStatus" - url: "/api/sysStatus"
- name: "Icanhazip"
icon: "fas fa-joint"
subtitle: ""
url: "/api/internetCheck"
- name: "VPN"
icon: "fas fa-joint"
subtitle: ""
url: "/api/vpnCheck"
- name: "VPN"
icon: "fas fa-joint"
subtitle: ""
url: "/api/lancianoCheck"
- name: "TV Status" - name: "TV Status"
icon: "fas fa-heartbeat" icon: "fas fa-tv"
items: items:
- name: "Tv Hat Frequency" # - name: ""
icon: "fas fa-broadcast-tower" # icon: "fas fa-broadcast-tower"
subtitle: "" # subtitle: ""
url: "/api/dvbStatus?t=1" - url: "/api/dvbtuner"
- name: "Tv Hat Channel"
icon: "fas fa-tv"
subtitle: ""
url: "/api/dvbChannel?t=1"
- name: "USB tuner Frequency"
icon: "fas fa-broadcast-tower"
subtitle: ""
url: "/api/dvbStatus?t=0"
- name: "USB Tv Channel"
icon: "fas fa-tv"
subtitle: ""
url: "/api/dvbChannel?t=0"
+41 -16
View File
@@ -28,9 +28,9 @@
:links="config.links" :links="config.links"
@navbar:toggle="showMenu = !showMenu" @navbar:toggle="showMenu = !showMenu"
> >
<Clock />
<DarkMode @updated="isDark = $event" /> <DarkMode @updated="isDark = $event" />
<!-- <SettingToggle
<SettingToggle
@updated="vlayout = $event" @updated="vlayout = $event"
name="vlayout" name="vlayout"
icon="fa-list" icon="fa-list"
@@ -43,7 +43,7 @@
@search:focus="showMenu = true" @search:focus="showMenu = true"
@search:open="navigateToFirstService" @search:open="navigateToFirstService"
@search:cancel="filterServices" @search:cancel="filterServices"
/> /> -->
</Navbar> </Navbar>
</div> </div>
@@ -60,7 +60,9 @@
<!-- Horizontal layout --> <!-- Horizontal layout -->
<div v-if="!vlayout || filter" class="columns is-multiline"> <div v-if="!vlayout || filter" class="columns is-multiline">
<template v-for="group in services"> <template v-for="group in services">
<h2 v-if="group.name" class="column is-full group-title"> <h2 v-if="group.name"
:key="group.name"
class="column is-full group-title">
<i v-if="group.icon" :class="group.icon"></i> <i v-if="group.icon" :class="group.icon"></i>
{{ group.name }} {{ group.name }}
</h2> </h2>
@@ -71,15 +73,14 @@
:class="['column', `is-${12 / config.columns}`]" :class="['column', `is-${12 / config.columns}`]"
/> />
</template> </template>
<template v-for="group in external"> <template v-for="group in wsdata">
<h2 v-if="group.name" class="column is-full group-title"> <h2 v-if="group.name" v-bind:key="group.name" class="column is-full group-title">
<i v-if="group.icon" :class="group.icon"></i> <i v-if="group.icon" :class="group.icon"></i>
{{ group.name }} {{ group.name }}
</h2> </h2>
<External <External
v-for="item in group.items" v-bind:item="group.data"
:key="item.name" :key="group.id"
v-bind:item="item"
:class="['column', `is-${12 / config.columns}`]" :class="['column', `is-${12 / config.columns}`]"
/> />
</template> </template>
@@ -105,7 +106,7 @@
:key="item.url" :key="item.url"
/> />
</div> </div>
<div <!-- <div
:class="['column', `is-${12 / config.columns}`]" :class="['column', `is-${12 / config.columns}`]"
v-for="group in external" v-for="group in external"
:key="group.name" :key="group.name"
@@ -119,7 +120,7 @@
v-bind:item="item" v-bind:item="item"
:key="item.url" :key="item.url"
/> />
</div> </div> -->
</div> </div>
</div> </div>
</div> </div>
@@ -146,10 +147,11 @@ import ConnectivityChecker from "./components/ConnectivityChecker.vue";
import Service from "./components/Service.vue"; import Service from "./components/Service.vue";
import External from "./components/External.vue"; import External from "./components/External.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 SettingToggle from "./components/SettingToggle.vue";
import DarkMode from "./components/DarkMode.vue"; import DarkMode from "./components/DarkMode.vue";
import DynamicTheme from "./components/DynamicTheme.vue"; import DynamicTheme from "./components/DynamicTheme.vue";
import Clock from "./components/Clock.vue";
import defaultConfig from "./assets/defaults.yml"; import defaultConfig from "./assets/defaults.yml";
@@ -161,10 +163,11 @@ export default {
Service, Service,
External, External,
Message, Message,
SearchInput, // SearchInput,
SettingToggle, // SettingToggle,
DarkMode, DarkMode,
DynamicTheme, DynamicTheme,
Clock,
}, },
data: function () { data: function () {
return { return {
@@ -175,7 +178,12 @@ export default {
filter: "", filter: "",
vlayout: true, vlayout: true,
isDark: null, isDark: null,
showMenu: false, showMenu: true,
wsconnection: null,
wsapi: null,
dvbtuner: null,
sysstat: null,
wsdata:[],
}; };
}, },
created: async function () { created: async function () {
@@ -185,8 +193,24 @@ export default {
this.services = this.config.services; this.services = this.config.services;
this.external = this.config.external; this.external = this.config.external;
document.title = `${this.config.title} | ${this.config.subtitle}`; document.title = `${this.config.title} | ${this.config.subtitle}`;
// ws section
let that = this;
this.wsconnection = new WebSocket("wss://sco.snakes.wtf/api");
this.wsconnection.onmessage = function(event) {
let tmp = JSON.parse(event.data);
that.wsdata[tmp.id] = tmp;
console.log("recv(onmessage)",JSON.parse(event.data)) //recv
}
// this.wsconnection.onopen = function(event) {}
//that.sendMessage("helo");
// }
}, },
methods: { methods: {
sendMessage: function(message) {
this.wsconnection.send(message);
},
getConfig: function (path = "config.yml") { getConfig: function (path = "config.yml") {
return fetch(path).then((response) => { return fetch(path).then((response) => {
if (!response.ok) { if (!response.ok) {
@@ -240,6 +264,7 @@ export default {
} }
} }
} }
//TODO filter external
this.services = [ this.services = [
{ {
+3 -2
View File
@@ -143,7 +143,7 @@ body {
a { a {
color: var(--text-header); color: var(--text-header);
padding: 8px 12px; padding: 6px 6px;
&:hover, &:hover,
&:focus { &:focus {
color: var(--text-header); color: var(--text-header);
@@ -221,6 +221,7 @@ body {
transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
a { a {
color: var(--link-color);
outline: none; outline: none;
} }
} }
@@ -331,7 +332,7 @@ body {
line-height: 1rem; line-height: 1rem;
vertical-align: middle; vertical-align: middle;
cursor: pointer; cursor: pointer;
color: #3273dc; color: #f18409;
} }
} }
} }
+37
View File
@@ -0,0 +1,37 @@
<template>
<a aria-label="clock" class="navbar-item is-inline-block-mobile" style="font-family:monospace; font-size:0.9em;">
<i class="fas fa-clock"></i>
{{clock}}
</a>
</template>
<script>
export default {
name: "Clock",
data: function () {
return {
clock: '',
time: 0
};
},
created: function () {
//let today = new Date();
//this.clock =`${today.toLocaleString("it-IT",{timeZone: "Europe/Rome"}).split(",")[1].trim()} Rome\t${today.toLocaleString("it-IT", {timeZone: "UTC"}).split(",")[1].trim()} UTC\t${today.toLocaleString("it-IT",{timeZone:"America/Chicago"}).split(",")[1].trim()} CT\t${today.toLocaleString("it-IT", {timeZone: "America/Los_Angeles"}).split(",")[1].trim()} PT`;
// this.time = setTimeout(this.updateClock,1000);
this.updateClock()
},
methods: {
updateClock: function () {
let today = new Date();
this.clock =`${today.toLocaleString("it-IT", {timeZone: "UTC"}).split(",")[1].trim()} UTC ${today.toLocaleString("it-IT",{timeZone:"America/Chicago"}).split(",")[1].trim()} CT ${today.toLocaleString("it-IT", {timeZone: "America/Los_Angeles"}).split(",")[1].trim()} PT`;
this.time = setTimeout(this.updateClock,1000);
},
cancelAutoUpdate: function () {
clearTimeout(this.time);
}
},
beforeDestroy() {
this.cancelAutoUpdate();
},
};
</script>
+31 -18
View File
@@ -1,18 +1,18 @@
<template> <template>
<div> <div>
<div class="card"> <div v-for="diocane in item" :key="diocane.id" class="card">
<a href="#" :target="item.target" rel="noreferrer"> <a href="#" :target="item.target" rel="noreferrer">
<div class="card-content"> <div class="card-content">
<div class="media"> <div class="media">
<!-- <div class="card-content "> --> <!-- <div class="card-content "> -->
<div v-if="item.icon" class="media-left"> <div v-if="diocane.icon" class="media-left">
<figure class="image is-48x48"> <figure class="image is-48x48">
<i style="font-size: 35px;" :class="item.icon"></i> <i style="font-size: 35px" :class="diocane.icon"></i>
</figure> </figure>
</div> </div>
<div class="media-content"> <div class="media-content">
<p class="title is-5">{{ item.name }}</p> <p class="title is-5">{{ diocane.id }}</p>
<p class="subtitle is-6">{{ response.data }}</p> <p class="subtitle is-6">{{ diocane.data }}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -30,22 +30,35 @@ export default {
data: function () { data: function () {
return { return {
response: null, response: null,
timer: "",
}; };
}, },
mounted: async function () { mounted: function () {
let response = await fetch(this.item.url, { // this.fetchList();
headers: { // this.timer = setInterval(this.fetchList, 10000);
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw Error(response.statusText);
}
this.response = await response.json();
if (this.response.stderr !== "") {
console.error(this.response.stderr);
}
}, },
methods: {
fetchList: async function () {
let response = await fetch(this.item.url, {
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw Error(response.statusText);
}
this.response = await response.json();
if (this.response.stderr !== "") {
//console.error(this.response.stderr);
}
},
// cancelAutoUpdate: function () {
// clearInterval(this.timer);
// }
},
// beforeDestroy() {
// this.cancelAutoUpdate();
// },
}; };
</script> </script>
+9 -8
View File
@@ -2,25 +2,25 @@
<div v-cloak v-if="links" class="container-fluid"> <div v-cloak v-if="links" class="container-fluid">
<nav class="navbar" role="navigation" aria-label="main navigation"> <nav class="navbar" role="navigation" aria-label="main navigation">
<div class="container"> <div class="container">
<div class="navbar-brand"> <!-- <div class="navbar-brand">
<a <a
role="button" role="button"
aria-label="menu" aria-label="menu"
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')" @click="$emit('navbar-toggle')"
> >
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
<span aria-hidden="true"></span> <span aria-hidden="true"></span>
</a> </a>
</div> </div> -->
<div class="navbar-menu" :class="{ 'is-active': showMenu }"> <div class="navbar-menu" :class="{ 'is-active': showMenu }">
<div class="navbar-start"> <div class="navbar-start">
<!-- --> <!-- -->
<a <a
class="navbar-item" class="navbar-item is-inline-block-mobile"
rel="noreferrer" rel="noreferrer"
v-for="link in links" v-for="link in links"
:key="link.url" :key="link.url"
@@ -29,15 +29,16 @@
> >
<i <i
v-if="link.icon" v-if="link.icon"
style="margin-right: 6px;" style="margin-right: 2px;"
:class="link.icon" :class="link.icon"
></i> ></i>
{{ link.name }} {{ link.name }}
</a> </a>
<!-- --> <!-- -->
<slot></slot>
</div> </div>
<div class="navbar-end"> <div class="navbar-end">
<slot></slot> <!-- <slot></slot> -->
</div> </div>
</div> </div>
</div> </div>
@@ -51,13 +52,13 @@ export default {
props: { props: {
open: { open: {
type: Boolean, type: Boolean,
default: false, default: true,
}, },
links: Array, links: Array,
}, },
computed: { computed: {
showMenu: function () { showMenu: function () {
return this.open && this.isSmallScreen(); return this.open;// && this.isSmallScreen();
}, },
}, },
methods: { methods: {
+4 -4
View File
@@ -6,8 +6,8 @@
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 +20,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 +28,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 -1
View File
@@ -14,7 +14,7 @@ module.exports = {
appleMobileWebAppCapable: "yes", appleMobileWebAppCapable: "yes",
name: "Forbidden Fruit Dashboard", name: "Forbidden Fruit Dashboard",
short_name: "FFruit", short_name: "FFruit",
theme_color: "#3367D6", theme_color: "#571962",
icons: [ icons: [
{ {
src: "./assets/icons/android-chrome-192x192.png", src: "./assets/icons/android-chrome-192x192.png",