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",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||