first commit
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
@@ -0,0 +1,15 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"],
|
||||
parserOptions: {
|
||||
parser: "babel-eslint",
|
||||
},
|
||||
rules: {
|
||||
"no-console": "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"vue/require-v-for-key": "off",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
dist/
|
||||
node_modules/
|
||||
.vscode/
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
const express = require('express');
|
||||
const compression = require('compression');
|
||||
const util = require('util');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const exec = util.promisify(require('child_process').exec);
|
||||
const readFileAsync = util.promisify(fs.readFile)
|
||||
|
||||
|
||||
const app = express(); // app is an instance of express
|
||||
|
||||
app.use(compression()); // enables gzip compression
|
||||
app.options('/dvbStatus', (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': ' GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Max-Age': 600
|
||||
})
|
||||
res.status(200).end();
|
||||
})
|
||||
|
||||
app.get('/dvbStatus', async (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
});
|
||||
//const { stdout, stderr } = await exec('/media/hd/data/projects/dvb-t-status');
|
||||
const { stdout, stderr } = await exec('DTV');
|
||||
let i = parseInt(stdout);
|
||||
i = i*1e-6; // i is in Hz; divide by 1M to have MHz
|
||||
res.send({
|
||||
"id": "dvb-t-status",
|
||||
"data": `Frontend Frequency: ${i} Mhz`,
|
||||
"stderr": stderr
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
app.options('/dvbChannel', (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': ' GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Max-Age': 600
|
||||
})
|
||||
res.status(200).end();
|
||||
})
|
||||
|
||||
app.get('/dvbChannel', async (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
});
|
||||
const { stdout, stderr } = await exec('DTV');
|
||||
let i = parseInt(stdout);
|
||||
i = i*1e-6; // i is in Hz; divide by 1M to have MHz
|
||||
readFileAsync(path.join(__dirname,"frequencies.json"))
|
||||
.then(res2 => {
|
||||
const db = JSON.parse(res2);
|
||||
//console.log(db[i]);
|
||||
res.send({
|
||||
"id": `dvb-t-channel ${db[i]["name"]}`,
|
||||
"data": db[i]["channels"].toString(),
|
||||
"stderr": stderr
|
||||
});
|
||||
})
|
||||
.catch(err => console.error(err))
|
||||
|
||||
|
||||
});
|
||||
|
||||
app.options('/internetCheck', async (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': ' GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Max-Age': 600
|
||||
})
|
||||
res.status(200).end();
|
||||
})
|
||||
app.get('/internetCheck', async (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
});
|
||||
const { stdout, stderr } = await exec('/media/hd/data/projects/icanhazip');
|
||||
res.send(
|
||||
{
|
||||
"id": "internet-check",
|
||||
"data": stdout,
|
||||
"stderr": stderr
|
||||
}
|
||||
)
|
||||
|
||||
});
|
||||
|
||||
app.options('/sysStatus', (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': ' GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
'Access-Control-Max-Age': 600
|
||||
})
|
||||
res.status(200).end();
|
||||
})
|
||||
|
||||
app.get('/sysStatus', async (req, res) => {
|
||||
res.set({
|
||||
'Access-Control-Allow-Origin': '*'
|
||||
});
|
||||
readFileAsync('/sys/class/thermal/thermal_zone0/temp')
|
||||
.then(res2=>
|
||||
{
|
||||
res.send(
|
||||
{
|
||||
"id": "sys-stats",
|
||||
"data": `Temp: ${parseFloat(res2)*1e-3.toFixed(2)} °C Load Avg: ${os.loadavg().map(x=>x.toFixed(2)).toString()}`,
|
||||
"stderr": ""
|
||||
}
|
||||
)
|
||||
})
|
||||
.catch(err=> {
|
||||
console.error(err)
|
||||
res.send(
|
||||
{
|
||||
"id": "sys-stats",
|
||||
"data": `Load Avg: ${os.loadavg().map(x=>x.toFixed(2)).toString()}`,
|
||||
"stderr": err
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
|
||||
app.use(express.static('/var/www/default')); // serve static build site
|
||||
|
||||
// app.get('*', (req, res) => {
|
||||
// res.sendFile(__dirname + '/public/index.html');
|
||||
// });
|
||||
|
||||
//app.listen(8000,"192.168.10.14", () => console.log('listen on 8000'));
|
||||
app.listen(process.env.NODE_PORT,'127.0.0.80', () => console.log('listen on 3000'));
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "forbidden-fruit-dashboard",
|
||||
"version": "2.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"bulma": "^0.8.2",
|
||||
"core-js": "^3.6.4",
|
||||
"js-yaml": "^3.14.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.4.1",
|
||||
"@vue/cli-plugin-eslint": "~4.4.1",
|
||||
"@vue/cli-plugin-pwa": "~4.4.1",
|
||||
"@vue/cli-service": "~4.4.1",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.2.0",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"prettier": "^2.0.5",
|
||||
"raw-loader": "^4.0.1",
|
||||
"sass": "^1.26.8",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 425 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" version="1.1">
|
||||
<path d="M 234 45.125 C 205.687 47.986, 173.877 58.506, 148.747 73.320 C 126.349 86.523, 102.089 108.205, 86.730 128.747 C 29.855 204.817, 29.851 307.105, 86.722 383.253 C 96.590 396.465, 115.594 415.392, 129.265 425.621 C 156.351 445.888, 186.971 458.997, 221.500 465.108 C 240.243 468.426, 271.757 468.426, 290.500 465.108 C 335.678 457.112, 373.822 437.369, 405.533 405.567 C 437.561 373.448, 457.110 335.690, 465.108 290.500 C 467.035 279.614, 467.409 274.004, 467.409 256 C 467.409 237.996, 467.035 232.386, 465.108 221.500 C 457.117 176.350, 437.513 138.460, 405.559 106.402 C 360.303 61, 297.804 38.677, 234 45.125 M 236 69.103 C 171.073 76.402, 114.777 116.345, 86.520 175.161 C 61.996 226.206, 61.996 285.794, 86.520 336.839 C 105.251 375.826, 136.655 407.141, 175.576 425.642 C 246.419 459.316, 329.484 445.672, 386.366 391.019 C 461.142 319.174, 463.217 200.777, 391.019 125.634 C 351.148 84.137, 292.501 62.750, 236 69.103 M 160.321 185.613 C 157.017 187.243, 154 192.200, 154 196 C 154 199.905, 157.036 204.789, 160.478 206.423 C 163.431 207.824, 174.110 208, 256 208 C 358.213 208, 353.195 208.324, 356.423 201.522 C 358.413 197.328, 358.413 194.672, 356.423 190.478 C 353.194 183.674, 358.236 183.998, 255.849 184.023 C 174.036 184.043, 163.137 184.224, 160.321 185.613 M 160.321 245.613 C 157.017 247.243, 154 252.200, 154 256 C 154 259.905, 157.036 264.789, 160.478 266.423 C 163.431 267.824, 174.110 268, 256 268 C 358.213 268, 353.195 268.324, 356.423 261.522 C 358.413 257.328, 358.413 254.672, 356.423 250.478 C 353.194 243.674, 358.236 243.998, 255.849 244.023 C 174.036 244.043, 163.137 244.224, 160.321 245.613 M 160.321 305.613 C 157.017 307.243, 154 312.200, 154 316 C 154 319.905, 157.036 324.789, 160.478 326.423 C 163.431 327.824, 174.110 328, 256 328 C 358.213 328, 353.195 328.324, 356.423 321.522 C 358.413 317.328, 358.413 314.672, 356.423 310.478 C 353.194 303.674, 358.236 303.998, 255.849 304.023 C 174.036 304.043, 163.137 304.224, 160.321 305.613" stroke="none" fill="black" fill-rule="evenodd"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "Dashboard",
|
||||
"short_name": "homer",
|
||||
"theme_color": "#3367D6",
|
||||
"icons": [
|
||||
{
|
||||
"src": "./assets/icons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/android-chrome-maskable-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/android-chrome-maskable-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/apple-touch-icon-60x60.png",
|
||||
"sizes": "60x60",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/apple-touch-icon-76x76.png",
|
||||
"sizes": "76x76",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/apple-touch-icon-120x120.png",
|
||||
"sizes": "120x120",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/apple-touch-icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/apple-touch-icon-180x180.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/apple-touch-icon.png",
|
||||
"sizes": "180x180",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/favicon-16x16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/favicon-32x32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/msapplication-icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "./assets/icons/mstile-150x150.png",
|
||||
"sizes": "150x150",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,120 @@
|
||||
---
|
||||
# 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
|
||||
#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
|
||||
theme: default
|
||||
colors:
|
||||
light:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#f5f5f5"
|
||||
card-background: "#ffffff"
|
||||
text: "#363636"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#303030"
|
||||
text-subtitle: "#424242"
|
||||
card-shadow: rgba(0, 0, 0, 0.1)
|
||||
link-hover: "#363636"
|
||||
dark:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#131313"
|
||||
card-background: "#2b2b2b"
|
||||
text: "#eaeaea"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#fafafa"
|
||||
text-subtitle: "#f5f5f5"
|
||||
card-shadow: rgba(0, 0, 0, 0.4)
|
||||
link-hover: "#ffdd57"
|
||||
|
||||
# Optional message
|
||||
#message:
|
||||
#url: https://b4bz.io
|
||||
# style: "is-dark" # See https://bulma.io/documentation/components/message/#colors for styling options.
|
||||
#title: "👋 Demo !"
|
||||
#content: "This is a dummy homepage demo. <br /> Find more information on <a href='https://github.com/bastienwirtz/homer'>github.com/bastienwirtz/homer</a>"
|
||||
|
||||
# Optional navbar
|
||||
# links: [] # Allows for navbar (dark mode, layout, and search) without any links
|
||||
#links:
|
||||
# - name: "Contribute"
|
||||
# icon: "fab fa-github"
|
||||
# url: "https://github.com/bastienwirtz/homer"
|
||||
# target: "_blank" # optionnal html a tag target attribute
|
||||
# - name: "Wiki"
|
||||
# icon: "fas fa-book"
|
||||
# url: "https://www.wikipedia.org/"
|
||||
|
||||
# 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: "Pihole"
|
||||
icon: "fab fa-raspberry-pi"
|
||||
subtitle: "192.168.10.12"
|
||||
url: "http://pihole.lan/admin"
|
||||
target: "_blank"
|
||||
- name: "TvHeadEnd"
|
||||
icon: "fas fa-tv"
|
||||
subtitle: "192.168.10.13"
|
||||
url: "http://tvheadend.lan"
|
||||
target: "_blank"
|
||||
- name: "SnakesHub"
|
||||
icon: "fab fa-git"
|
||||
subtitle: "dev.jagernet.ga"
|
||||
url: "https://dev.jagernet.ga"
|
||||
target: "_blank"
|
||||
- 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"
|
||||
- name: "Bedroom Switch"
|
||||
icon: "fas fa-ethernet"
|
||||
subtitle: "192.168.10.2"
|
||||
url: "http://192.168.10.2"
|
||||
target: "_blank"
|
||||
- name: "Main Switch"
|
||||
icon: "fas fa-ethernet"
|
||||
subtitle: "192.168.10.3"
|
||||
url: "http://192.168.10.3"
|
||||
target: "_blank"
|
||||
external:
|
||||
- name: "Status"
|
||||
icon: "fas fa-heartbeat"
|
||||
items:
|
||||
- name: "Test"
|
||||
icon: "fas fa-joint"
|
||||
subtitle: "192.168.10.1"
|
||||
url: "/dvbStatus"
|
||||
- name: "Test2"
|
||||
icon: "fas fa-joint"
|
||||
subtitle: "192.168.10.1"
|
||||
url: "/internetCheck"
|
||||
- name: "Test3"
|
||||
icon: "fas fa-joint"
|
||||
subtitle: "192.168.10.1"
|
||||
url: "/dvbChannel"
|
||||
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
|
||||
<meta name="robots" content="noindex">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.png">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,263 @@
|
||||
<template>
|
||||
<div
|
||||
id="app"
|
||||
v-if="config"
|
||||
:class="[
|
||||
`theme-${config.theme}`,
|
||||
isDark ? 'is-dark' : 'is-light',
|
||||
!config.footer ? 'no-footer' : '',
|
||||
]"
|
||||
>
|
||||
<DynamicTheme :themes="config.colors" />
|
||||
<div id="bighead">
|
||||
<section v-if="config.header" class="first-line">
|
||||
<div v-cloak class="container">
|
||||
<div class="logo">
|
||||
<img v-if="config.logo" :src="config.logo" alt="dashboard logo" />
|
||||
<i v-if="config.icon" :class="config.icon"></i>
|
||||
</div>
|
||||
<div class="dashboard-title">
|
||||
<span class="headline">{{ config.subtitle }}</span>
|
||||
<h1>{{ config.title }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Navbar
|
||||
:open="showMenu"
|
||||
:links="config.links"
|
||||
@navbar:toggle="showMenu = !showMenu"
|
||||
>
|
||||
<DarkMode @updated="isDark = $event" />
|
||||
|
||||
<SettingToggle
|
||||
@updated="vlayout = $event"
|
||||
name="vlayout"
|
||||
icon="fa-list"
|
||||
iconAlt="fa-columns"
|
||||
/>
|
||||
|
||||
<SearchInput
|
||||
class="navbar-item is-inline-block-mobile"
|
||||
@input="filterServices"
|
||||
@search:focus="showMenu = true"
|
||||
@search:open="navigateToFirstService"
|
||||
@search:cancel="filterServices"
|
||||
/>
|
||||
</Navbar>
|
||||
</div>
|
||||
|
||||
<section id="main-section" class="section">
|
||||
<div v-cloak class="container">
|
||||
<ConnectivityChecker
|
||||
v-if="config.connectivityCheck"
|
||||
@network:status-update="offline = $event"
|
||||
/>
|
||||
<div v-if="!offline">
|
||||
<!-- Optional messages -->
|
||||
<Message :item="config.message" />
|
||||
|
||||
<!-- Horizontal layout -->
|
||||
<div v-if="!vlayout || filter" class="columns is-multiline">
|
||||
<template v-for="group in services">
|
||||
<h2 v-if="group.name" class="column is-full group-title">
|
||||
<i v-if="group.icon" :class="group.icon"></i>
|
||||
{{ group.name }}
|
||||
</h2>
|
||||
<Service
|
||||
v-for="item in group.items"
|
||||
:key="item.name"
|
||||
v-bind:item="item"
|
||||
:class="['column', `is-${12 / config.columns}`]"
|
||||
/>
|
||||
</template>
|
||||
<template v-for="group in external">
|
||||
<h2 v-if="group.name" class="column is-full group-title">
|
||||
<i v-if="group.icon" :class="group.icon"></i>
|
||||
{{ group.name }}
|
||||
</h2>
|
||||
<External
|
||||
v-for="item in group.items"
|
||||
:key="item.name"
|
||||
v-bind:item="item"
|
||||
:class="['column', `is-${12 / config.columns}`]"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Vertical layout -->
|
||||
<div
|
||||
v-if="!filter && vlayout"
|
||||
class="columns is-multiline layout-vertical"
|
||||
>
|
||||
<div
|
||||
:class="['column', `is-${12 / config.columns}`]"
|
||||
v-for="group in services"
|
||||
:key="group.name"
|
||||
>
|
||||
<h2 v-if="group.name" class="group-title">
|
||||
<i v-if="group.icon" :class="group.icon"></i>
|
||||
{{ group.name }}
|
||||
</h2>
|
||||
<Service
|
||||
v-for="item in group.items"
|
||||
v-bind:item="item"
|
||||
:key="item.url"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
:class="['column', `is-${12 / config.columns}`]"
|
||||
v-for="group in external"
|
||||
:key="group.name"
|
||||
>
|
||||
<h2 v-if="group.name" class="group-title">
|
||||
<i v-if="group.icon" :class="group.icon"></i>
|
||||
{{ group.name }}
|
||||
</h2>
|
||||
<External
|
||||
v-for="item in group.items"
|
||||
v-bind:item="item"
|
||||
:key="item.url"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div
|
||||
class="content has-text-centered"
|
||||
v-if="config.footer"
|
||||
v-html="config.footer"
|
||||
></div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const jsyaml = require("js-yaml");
|
||||
const merge = require("lodash.merge");
|
||||
|
||||
import Navbar from "./components/Navbar.vue";
|
||||
import ConnectivityChecker from "./components/ConnectivityChecker.vue";
|
||||
import Service from "./components/Service.vue";
|
||||
import External from "./components/External.vue";
|
||||
import Message from "./components/Message.vue";
|
||||
import SearchInput from "./components/SearchInput.vue";
|
||||
import SettingToggle from "./components/SettingToggle.vue";
|
||||
import DarkMode from "./components/DarkMode.vue";
|
||||
import DynamicTheme from "./components/DynamicTheme.vue";
|
||||
|
||||
import defaultConfig from "./assets/defaults.yml";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Navbar,
|
||||
ConnectivityChecker,
|
||||
Service,
|
||||
External,
|
||||
Message,
|
||||
SearchInput,
|
||||
SettingToggle,
|
||||
DarkMode,
|
||||
DynamicTheme,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
config: null,
|
||||
services: null,
|
||||
external: null,
|
||||
offline: false,
|
||||
filter: "",
|
||||
vlayout: true,
|
||||
isDark: null,
|
||||
showMenu: false,
|
||||
};
|
||||
},
|
||||
created: async function () {
|
||||
const defaults = jsyaml.load(defaultConfig);
|
||||
let config = await this.getConfig();
|
||||
this.config = merge(defaults, config);
|
||||
this.services = this.config.services;
|
||||
this.external = this.config.external;
|
||||
document.title = `${this.config.title} | ${this.config.subtitle}`;
|
||||
},
|
||||
methods: {
|
||||
getConfig: function (path = "config.yml") {
|
||||
return fetch(path).then((response) => {
|
||||
if (!response.ok) {
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
|
||||
const that = this;
|
||||
return response
|
||||
.text()
|
||||
.then((body) => {
|
||||
return jsyaml.load(body);
|
||||
})
|
||||
.then(function (config) {
|
||||
if (config.externalConfig) {
|
||||
return that.getConfig(config.externalConfig);
|
||||
}
|
||||
return config;
|
||||
})
|
||||
.catch((error) => {
|
||||
return this.handleErrors("⚠️ Error loading configuration", error);
|
||||
});
|
||||
});
|
||||
},
|
||||
matchesFilter: function (item) {
|
||||
return (
|
||||
item.name.toLowerCase().includes(this.filter) ||
|
||||
(item.tag && item.tag.toLowerCase().includes(this.filter))
|
||||
);
|
||||
},
|
||||
navigateToFirstService: function (target) {
|
||||
try {
|
||||
const service = this.services[0].items[0];
|
||||
window.open(service.url, target || service.target || "_self");
|
||||
} catch (error) {
|
||||
console.warning("fail to open service");
|
||||
}
|
||||
},
|
||||
filterServices: function (filter) {
|
||||
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,
|
||||
},
|
||||
];
|
||||
},
|
||||
handleErrors: function (title, content) {
|
||||
return {
|
||||
message: {
|
||||
title: title,
|
||||
style: "is-danger",
|
||||
content: content,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,337 @@
|
||||
@charset "utf-8";
|
||||
|
||||
@import "./webfonts/webfonts.scss";
|
||||
|
||||
@import "bulma";
|
||||
|
||||
// Themes import
|
||||
@import "./themes/sui.scss";
|
||||
|
||||
@mixin ellipsis() {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Raleway", sans-serif;
|
||||
height: 100%;
|
||||
|
||||
#app {
|
||||
min-height: 100%;
|
||||
background-color: var(--background);
|
||||
color: var(--text);
|
||||
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
|
||||
|
||||
a {
|
||||
&:hover {
|
||||
color: var(--link-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--text-title);
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--text-subtitle);
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-background);
|
||||
box-shadow: 0 2px 15px 0 var(--card-shadow);
|
||||
&:hover {
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
background-color: var(--card-background);
|
||||
.message-body {
|
||||
color: var(--text);
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: var(--card-background);
|
||||
box-shadow: 0 2px 15px 0 var(--card-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: "Lato", sans-serif;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.7rem;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.fas,
|
||||
.fab,
|
||||
.far {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
color: var(--highlight-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#bighead {
|
||||
color: var(--text-header);
|
||||
|
||||
.dashboard-title {
|
||||
padding: 6px 0 0 80px;
|
||||
}
|
||||
|
||||
.first-line {
|
||||
height: 100px;
|
||||
vertical-align: center;
|
||||
background-color: var(--highlight-primary);
|
||||
|
||||
h1 {
|
||||
margin-top: -12px;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.headline {
|
||||
margin-top: 5px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
height: 80px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
float: left;
|
||||
i {
|
||||
vertical-align: top;
|
||||
padding: 8px 15px;
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
img {
|
||||
padding: 10px;
|
||||
max-height: 70px;
|
||||
max-width: 70px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.navbar,
|
||||
.navbar-menu {
|
||||
background-color: var(--highlight-secondary);
|
||||
|
||||
a {
|
||||
color: var(--text-header);
|
||||
padding: 8px 12px;
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--text-header);
|
||||
background-color: var(--highlight-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
.navbar-end {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
#main-section {
|
||||
margin-bottom: 2rem;
|
||||
padding: 0;
|
||||
|
||||
h2 {
|
||||
padding-bottom: 0px;
|
||||
@include ellipsis();
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.1em;
|
||||
@include ellipsis();
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.9em;
|
||||
@include ellipsis();
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1.2rem 0.75rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 45px;
|
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
.message-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.message-body {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-content {
|
||||
overflow: hidden;
|
||||
text-overflow: inherit;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: var(--highlight-secondary);
|
||||
background-color: var(--highlight-secondary);
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: -0.2rem;
|
||||
width: 3px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease-out;
|
||||
padding: 0;
|
||||
|
||||
.tag-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1);
|
||||
transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
|
||||
|
||||
a {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translate(0, -3px);
|
||||
|
||||
.tag {
|
||||
width: auto;
|
||||
color: #ffffff;
|
||||
padding: 0 0.75em;
|
||||
|
||||
.tag-text {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-content {
|
||||
height: 85px;
|
||||
padding: 1.3rem;
|
||||
}
|
||||
|
||||
.layout-vertical {
|
||||
.card {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.column div:first-of-type .card {
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
.column div:last-child .card {
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
color: #676767;
|
||||
font-size: 0.85rem;
|
||||
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
|
||||
}
|
||||
|
||||
.no-footer {
|
||||
#main-section {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
input {
|
||||
border: none;
|
||||
background-color: var(--highlight-hover);
|
||||
border-radius: 5px;
|
||||
margin-top: 2px;
|
||||
padding: 2px 12px 2px 30px;
|
||||
transition: all 100ms linear;
|
||||
color: #ffffff;
|
||||
height: 30px;
|
||||
width: 100px;
|
||||
|
||||
&:focus {
|
||||
color: #000000;
|
||||
width: 250px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.search-label::before {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
left: 16px;
|
||||
content: "\f002";
|
||||
font-weight: 900;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
&:focus-within .search-label::before {
|
||||
color: #6e6e6e;
|
||||
}
|
||||
}
|
||||
|
||||
.offline-message {
|
||||
text-align: center;
|
||||
margin: 35px 0;
|
||||
|
||||
i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
i.fa-redo-alt {
|
||||
font-size: 1.3rem;
|
||||
line-height: 1rem;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
color: #3273dc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
# Default configuration
|
||||
|
||||
title: "Forbidden Fruit dashboard"
|
||||
subtitle: "TANGIE"
|
||||
|
||||
header: true
|
||||
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: 3
|
||||
connectivityCheck: true
|
||||
|
||||
theme: default
|
||||
colors:
|
||||
light:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#f5f5f5"
|
||||
card-background: "#ffffff"
|
||||
text: "#363636"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#303030"
|
||||
text-subtitle: "#424242"
|
||||
card-shadow: rgba(0, 0, 0, 0.1)
|
||||
link-hover: "#363636"
|
||||
dark:
|
||||
highlight-primary: "#3367d6"
|
||||
highlight-secondary: "#4285f4"
|
||||
highlight-hover: "#5a95f5"
|
||||
background: "#131313"
|
||||
card-background: "#2b2b2b"
|
||||
text: "#eaeaea"
|
||||
text-header: "#ffffff"
|
||||
text-title: "#fafafa"
|
||||
text-subtitle: "#f5f5f5"
|
||||
card-shadow: rgba(0, 0, 0, 0.4)
|
||||
link-hover: "#ffdd57"
|
||||
|
||||
message: ~
|
||||
links: []
|
||||
services: []
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* SUI theme
|
||||
* Inpired by the great https://github.com/jeroenpardon/sui start page
|
||||
* Author: @bastienwirtz
|
||||
*/
|
||||
body #app.theme-sui {
|
||||
#bighead .dashboard-title {
|
||||
padding: 65px 0 0 12px;
|
||||
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
font-weight: bold;
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar .navbar-item:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.card,
|
||||
.card:hover {
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright (c) 2010-2014 by tyPoland Lukasz Dziedzic (team@latofonts.com) with Reserved Font Name "Lato"
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,95 @@
|
||||
Copyright (c) 2010, Matt McInerney (matt@pixelspread.com),
|
||||
Copyright (c) 2011, Pablo Impallari (www.impallari.com|impallari@gmail.com),
|
||||
Copyright (c) 2011, Rodrigo Fuenzalida (www.rfuenzalida.com|hello@rfuenzalida.com), with Reserved Font Name Raleway
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,23 @@
|
||||
/* raleway-regular - latin */
|
||||
@font-face {
|
||||
font-family: "Raleway";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local("Raleway"), local("Raleway-Regular"),
|
||||
url("./webfonts/raleway/raleway-v14-latin-regular.woff2") format("woff2"),
|
||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url("./webfonts/raleway/raleway-v14-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
}
|
||||
|
||||
/* lato-regular - latin */
|
||||
@font-face {
|
||||
font-family: "Lato";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local("Lato Regular"), local("Lato-Regular"),
|
||||
url("./webfonts/lato/lato-v16-latin-regular.woff2") format("woff2"),
|
||||
/* Chrome 26+, Opera 23+, Firefox 39+ */
|
||||
url("./webfonts/lato/lato-v16-latin-regular.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div v-if="offline" class="offline-message">
|
||||
<i class="fas fa-dizzy"></i>
|
||||
<h1>
|
||||
You're offline bro.
|
||||
<span @click="checkOffline"> <i class="fas fa-redo-alt"></i></span>
|
||||
</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ConnectivityChecker",
|
||||
data: function () {
|
||||
return {
|
||||
offline: false,
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
let that = this;
|
||||
this.checkOffline();
|
||||
|
||||
document.addEventListener(
|
||||
"visibilitychange",
|
||||
function () {
|
||||
if (document.visibilityState == "visible") {
|
||||
that.checkOffline();
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
checkOffline: function () {
|
||||
let that = this;
|
||||
return fetch(window.location.href + "?alive", {
|
||||
method: "HEAD",
|
||||
cache: "no-store",
|
||||
})
|
||||
.then(function () {
|
||||
that.offline = false;
|
||||
})
|
||||
.catch(function () {
|
||||
that.offline = true;
|
||||
})
|
||||
.finally(function () {
|
||||
that.$emit("network:status-update", that.offline);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<a
|
||||
v-on:click="toggleTheme()"
|
||||
aria-label="Toggle dark mode"
|
||||
class="navbar-item is-inline-block-mobile"
|
||||
>
|
||||
<i class="fas fa-adjust"></i>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Darkmode",
|
||||
data: function () {
|
||||
return {
|
||||
isDark: null,
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
this.isDark =
|
||||
"overrideDark" in localStorage
|
||||
? JSON.parse(localStorage.overrideDark)
|
||||
: matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
this.$emit("updated", this.isDark);
|
||||
},
|
||||
methods: {
|
||||
toggleTheme: function () {
|
||||
this.isDark = !this.isDark;
|
||||
localStorage.overrideDark = this.isDark;
|
||||
this.$emit("updated", this.isDark);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<DynamicStyle>
|
||||
/* light / dark theme switch based on system pref if available */ body #app
|
||||
{
|
||||
{{ getVars(themes.light) }}
|
||||
} @media (prefers-color-scheme: light), (prefers-color-scheme:
|
||||
no-preference) { body #app {
|
||||
{{ getVars(themes.light) }}
|
||||
} } @media (prefers-color-scheme: dark) { body #app { } } /* light / dark
|
||||
theme override base on user choice. */ body #app.is-dark {
|
||||
{{ getVars(themes.dark) }}
|
||||
} body #app.is-light {
|
||||
{{ getVars(themes.light) }}
|
||||
}
|
||||
</DynamicStyle>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "DynamicTheme",
|
||||
props: {
|
||||
themes: Object,
|
||||
},
|
||||
methods: {
|
||||
getVars: function (theme) {
|
||||
let vars = [];
|
||||
for (const themeVars in theme) {
|
||||
vars.push(`--${themeVars}: ${theme[themeVars]}`);
|
||||
}
|
||||
return vars.join(";");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card">
|
||||
<a href="#" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<!-- <div class="card-content "> -->
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px;" :class="item.icon"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-5">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">{{ response.data }}</p>
|
||||
</div>
|
||||
</div></div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "External",
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
response: null
|
||||
};
|
||||
},
|
||||
mounted: 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();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<article v-if="item" class="message" :class="item.style">
|
||||
<div v-if="item.title" class="message-header">
|
||||
<p>{{ item.title }}</p>
|
||||
</div>
|
||||
<div v-if="item.content" class="message-body" v-html="item.content"></div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Message",
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
created: function () {
|
||||
// Look for a new message if an endpoint is provided.
|
||||
let that = this;
|
||||
if (this.item && this.item.url) {
|
||||
this.getMessage(this.item.url).then(function (message) {
|
||||
// keep the original config value if no value is provided by the endpoint
|
||||
for (const prop of ["title", "style", "content"]) {
|
||||
if (prop in message && message[prop] !== null) {
|
||||
that.item[prop] = message[prop];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getMessage: function (url) {
|
||||
return fetch(url).then(function (response) {
|
||||
if (response.status != 200) {
|
||||
return;
|
||||
}
|
||||
return response.json();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div v-cloak v-if="links" class="container-fluid">
|
||||
<nav class="navbar" role="navigation" aria-label="main navigation">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a
|
||||
role="button"
|
||||
aria-label="menu"
|
||||
aria-expanded="false"
|
||||
class="navbar-burger"
|
||||
:class="{ 'is-active': showMenu }"
|
||||
v-on:click="$emit('navbar:toggle')"
|
||||
>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="navbar-menu" :class="{ 'is-active': showMenu }">
|
||||
<div class="navbar-start">
|
||||
<!-- -->
|
||||
<a
|
||||
class="navbar-item"
|
||||
rel="noreferrer"
|
||||
v-for="link in links"
|
||||
:key="link.url"
|
||||
:href="link.url"
|
||||
:target="link.target"
|
||||
>
|
||||
<i
|
||||
v-if="link.icon"
|
||||
style="margin-right: 6px;"
|
||||
:class="link.icon"
|
||||
></i>
|
||||
{{ link.name }}
|
||||
</a>
|
||||
<!-- -->
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Navbar",
|
||||
props: {
|
||||
open: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
links: Array,
|
||||
},
|
||||
computed: {
|
||||
showMenu: function () {
|
||||
return this.open && this.isSmallScreen();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isSmallScreen: function () {
|
||||
return window.matchMedia("screen and (max-width: 1023px)").matches;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="search-bar">
|
||||
<label for="search" class="search-label"></label>
|
||||
<input
|
||||
type="text"
|
||||
ref="search"
|
||||
:value="value"
|
||||
@input="$emit('input', $event.target.value.toLowerCase())"
|
||||
@keyup.enter.exact="$emit('search:open')"
|
||||
@keyup.alt.enter="$emit('search:open', '_blank')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "SearchInput",
|
||||
props: ["value"],
|
||||
mounted() {
|
||||
this._keyListener = function (event) {
|
||||
if (event.key === "/") {
|
||||
event.preventDefault();
|
||||
this.$emit("search:focus");
|
||||
this.$nextTick(() => {
|
||||
this.$refs.search.focus();
|
||||
});
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
this.$refs.search.value = "";
|
||||
this.$refs.search.blur();
|
||||
this.$emit("search:cancel");
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", this._keyListener.bind(this));
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("keydown", this._keyListener);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card">
|
||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px;" :class="item.icon"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">{{ item.subtitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Service",
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<a v-on:click="toggleSetting()" class="navbar-item is-inline-block-mobile">
|
||||
<span><i :class="['fas', 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>
|
||||
@@ -0,0 +1,19 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
|
||||
import "@fortawesome/fontawesome-free/css/all.css";
|
||||
|
||||
import "./assets/app.scss";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
Vue.component("DynamicStyle", {
|
||||
render: function (createElement) {
|
||||
return createElement("style", this.$slots.default);
|
||||
},
|
||||
});
|
||||
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
||||
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from "register-service-worker";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready() {
|
||||
console.log(
|
||||
"App is being served from cache by a service worker.\n" +
|
||||
"For more details, visit https://goo.gl/AFskqB"
|
||||
);
|
||||
},
|
||||
registered() {
|
||||
console.log("Service worker has been registered.");
|
||||
},
|
||||
cached() {
|
||||
console.log("Content has been cached for offline use.");
|
||||
},
|
||||
updatefound() {
|
||||
console.log("New content is downloading.");
|
||||
},
|
||||
updated() {
|
||||
console.log("New content is available; please refresh.");
|
||||
},
|
||||
offline() {
|
||||
console.log(
|
||||
"No internet connection found. App is running in offline mode."
|
||||
);
|
||||
},
|
||||
error(error) {
|
||||
console.error("Error during service worker registration:", error);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
module.exports = {
|
||||
chainWebpack: (config) => {
|
||||
config.module
|
||||
.rule("yaml")
|
||||
.test(/\.ya?ml$/)
|
||||
.use("raw-loader")
|
||||
.loader("raw-loader")
|
||||
.end();
|
||||
},
|
||||
publicPath: "",
|
||||
pwa: {
|
||||
manifestPath: "assets/manifest.json",
|
||||
appleMobileWebAppStatusBarStyle: "black",
|
||||
appleMobileWebAppCapable: "yes",
|
||||
name: "Forbidden Fruit Dashboard",
|
||||
short_name: "FF",
|
||||
theme_color: "#3367D6",
|
||||
icons: [
|
||||
{
|
||||
src: "./assets/icons/android-chrome-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/android-chrome-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/android-chrome-maskable-192x192.png",
|
||||
sizes: "192x192",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/android-chrome-maskable-512x512.png",
|
||||
sizes: "512x512",
|
||||
type: "image/png",
|
||||
purpose: "maskable",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/apple-touch-icon-60x60.png",
|
||||
sizes: "60x60",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/apple-touch-icon-76x76.png",
|
||||
sizes: "76x76",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/apple-touch-icon-120x120.png",
|
||||
sizes: "120x120",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/apple-touch-icon-152x152.png",
|
||||
sizes: "152x152",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/apple-touch-icon-180x180.png",
|
||||
sizes: "180x180",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/apple-touch-icon.png",
|
||||
sizes: "180x180",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/favicon-16x16.png",
|
||||
sizes: "16x16",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/favicon-32x32.png",
|
||||
sizes: "32x32",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/msapplication-icon-144x144.png",
|
||||
sizes: "144x144",
|
||||
type: "image/png",
|
||||
},
|
||||
{
|
||||
src: "./assets/icons/mstile-150x150.png",
|
||||
sizes: "150x150",
|
||||
type: "image/png",
|
||||
},
|
||||
],
|
||||
iconPaths: {
|
||||
favicon32: "assets/icons/favicon-32x32.png",
|
||||
favicon16: "assets/icons/favicon-16x16.png",
|
||||
appleTouchIcon: "assets/icons/apple-touch-icon-152x152.png",
|
||||
maskIcon: "assets/icons/safari-pinned-tab.svg",
|
||||
msTileImage: "assets/icons/msapplication-icon-144x144.png",
|
||||
},
|
||||
},
|
||||
};
|
||||