Working
This commit is contained in:
parent
1fe69cd88f
commit
f9c74c2aac
|
@ -0,0 +1,79 @@
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
font-family: Helvetica, sans-serif;
|
||||||
|
background-color: #222;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
overflow-x: auto;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12pt;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
nav img {
|
||||||
|
max-height: 2em;
|
||||||
|
}
|
||||||
|
nav a {
|
||||||
|
padding: 0.5em 1.5em;
|
||||||
|
color: #aaa;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
nav a[data-no-menu] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
nav a:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
nav a.active {
|
||||||
|
background: #8b8;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
iframe {
|
||||||
|
display: block;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
margin: auto 2em;
|
||||||
|
}
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
max-width: 40em;
|
||||||
|
margin: auto;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.icons a {
|
||||||
|
background-color: #444;
|
||||||
|
color: #fff;
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.icons a img,
|
||||||
|
.icons a canvas {
|
||||||
|
max-width: 75%;
|
||||||
|
max-height: 75%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
|
@ -1,18 +1,18 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Webstat</title>
|
<title>Deer Grove</title>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<script src="main.mjs" type="module"></script>
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<style>
|
<link rel="icon" href="deergrove.png">
|
||||||
#pie {
|
<link rel="stylesheet" href="index.css">
|
||||||
max-width: 50px;
|
<script src="index.mjs" type="module"></script>
|
||||||
}
|
</head>
|
||||||
</style>
|
<body>
|
||||||
</head>
|
<nav>
|
||||||
|
</nav>
|
||||||
<body>
|
<section id="app">
|
||||||
<div id="pie"></div>
|
<iframe></iframe>
|
||||||
<div id="chart"></div>
|
</section>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
import * as WebStat from "./webstat.mjs"
|
||||||
|
|
||||||
|
const Millisecond = 1
|
||||||
|
const Second = 1000 * Millisecond
|
||||||
|
const Minute = 60 * Second
|
||||||
|
|
||||||
|
class StatApp {
|
||||||
|
constructor(parent) {
|
||||||
|
this.stat = new WebStat.Stat()
|
||||||
|
this.chart = new WebStat.PieChart(parent, this.stat)
|
||||||
|
|
||||||
|
setInterval(()=>this.update(), 2 * Second)
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
async update() {
|
||||||
|
this.stat.update()
|
||||||
|
this.chart.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let frames = {}
|
||||||
|
function activate(event, element) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
let parent = element.parentElement
|
||||||
|
for (let e of parent.getElementsByClassName("active")) {
|
||||||
|
e.classList.remove("active")
|
||||||
|
}
|
||||||
|
element.classList.add("active")
|
||||||
|
let href = element.href
|
||||||
|
let app = document.querySelector("#app")
|
||||||
|
|
||||||
|
let frame = frames[href]
|
||||||
|
if (frame) {
|
||||||
|
frame.dispatchEvent(new Event("load"))
|
||||||
|
} else {
|
||||||
|
frame = app.appendChild(document.createElement("iframe"))
|
||||||
|
frame.addEventListener("load", e => frameLoaded(frame))
|
||||||
|
frame.src = href
|
||||||
|
frames[href] = frame
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let fhref in frames) {
|
||||||
|
let f = frames[fhref]
|
||||||
|
if (fhref == href) {
|
||||||
|
f.classList.remove("hidden")
|
||||||
|
} else {
|
||||||
|
f.classList.add("hidden")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function frameLoaded(frame) {
|
||||||
|
let doc = frame.contentDocument
|
||||||
|
if (doc.title.length > 0) {
|
||||||
|
document.title = doc.title
|
||||||
|
}
|
||||||
|
let icon = document.querySelector("link[rel~='icon']")
|
||||||
|
let dicon = doc.querySelector("link[rel~='icon']")
|
||||||
|
if (dicon) {
|
||||||
|
icon.href = dicon.href
|
||||||
|
} else {
|
||||||
|
icon.href = defaultIcon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultIcon = null
|
||||||
|
async function init() {
|
||||||
|
let doc = document.querySelector("iframe").contentDocument
|
||||||
|
|
||||||
|
defaultIcon = document.querySelector("link[rel~='icon']").href
|
||||||
|
|
||||||
|
for (let l of document.head.querySelectorAll("style")) {
|
||||||
|
doc.head.appendChild(l.cloneNode(true))
|
||||||
|
}
|
||||||
|
for (let l of document.head.querySelectorAll("link[rel='stylesheet']")) {
|
||||||
|
doc.head.appendChild(l.cloneNode())
|
||||||
|
}
|
||||||
|
for (let f of document.querySelectorAll("#app iframe")) {
|
||||||
|
frames[f.src] = f
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let icons = doc.body.appendChild(doc.createElement("section"))
|
||||||
|
icons.classList.add("icons")
|
||||||
|
|
||||||
|
let nav = document.querySelector("nav")
|
||||||
|
let resp = await fetch("portal.json")
|
||||||
|
let obj = await resp.json()
|
||||||
|
for (let app of obj) {
|
||||||
|
let hlink = null
|
||||||
|
if (app.target != "_blank") {
|
||||||
|
hlink = nav.appendChild(document.createElement("a"))
|
||||||
|
hlink.href = app.href
|
||||||
|
hlink.textContent = app.title
|
||||||
|
if (app.target) {
|
||||||
|
hlink.target = app.target
|
||||||
|
} else {
|
||||||
|
hlink.addEventListener("click", event => activate(event, hlink))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dlink = icons.appendChild(doc.createElement("a"))
|
||||||
|
dlink.href = app.href
|
||||||
|
if (app.target) {
|
||||||
|
dlink.target = app.target
|
||||||
|
} else {
|
||||||
|
dlink.addEventListener("click", event => activate(event, hlink))
|
||||||
|
}
|
||||||
|
if (app.icon) {
|
||||||
|
let icon = dlink.appendChild(doc.createElement("img"))
|
||||||
|
icon.src = app.icon
|
||||||
|
icon.alt = app.title
|
||||||
|
icon.title = app.title
|
||||||
|
icon.style.objectFit = "cover"
|
||||||
|
} else if (app.app) {
|
||||||
|
if (app.app == "stat") {
|
||||||
|
new StatApp(dlink)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let text = dlink.appendChild(doc.createElement("div"))
|
||||||
|
text.textContent = app.title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === "loading") {
|
||||||
|
document.addEventListener("DOMContentLoaded", init)
|
||||||
|
} else {
|
||||||
|
init()
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Movies",
|
||||||
|
"href": "https://deergrove.woozle.org/radarr/",
|
||||||
|
"icon": "/radarr/Content/Images/logo.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Episodes",
|
||||||
|
"href": "https://deergrove.woozle.org/sonarr/",
|
||||||
|
"icon": "/sonarr/Content/Images/logo.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Music",
|
||||||
|
"href": "https://deergrove.woozle.org/lidarr/",
|
||||||
|
"icon": "/lidarr/Content/Images/logo.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Books",
|
||||||
|
"href": "https://deergrove.woozle.org/readarr/",
|
||||||
|
"icon": "/readarr/Content/Images/logo.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Media Sucker",
|
||||||
|
"href": "https://deergrove.woozle.org/sucker/",
|
||||||
|
"icon": "/sucker/cd-dvd.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Searcher",
|
||||||
|
"href": "https://deergrove.woozle.org/prowlarr/",
|
||||||
|
"icon": "/prowlarr/Content/Images/logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Usenet",
|
||||||
|
"href": "https://deergrove.woozle.org/nzbget/",
|
||||||
|
"icon": "/nzbget/img/favicon-256x256.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "BitTorrent",
|
||||||
|
"href": "https://deergrove.woozle.org/transmission/web/",
|
||||||
|
"icon": "/transmission/web/images/webclip-icon.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "3D Printer",
|
||||||
|
"href": "https://deergrove.woozle.org/octoprint/",
|
||||||
|
"icon": "/octoprint/static/img/logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Git",
|
||||||
|
"href": "https://git.woozle.org/",
|
||||||
|
"icon": "https://git.woozle.org/assets/img/logo.svg",
|
||||||
|
"target": "_blank"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Storage",
|
||||||
|
"href": "https://drive.woozle.org/",
|
||||||
|
"icon": "https://drive.woozle.org/storage/public/icons/cloud-folder.png",
|
||||||
|
"target": "_blank"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Genealogy",
|
||||||
|
"href": "https://ancestry.woozle.org/",
|
||||||
|
"icon": "https://ancestry.woozle.org/images/arbre_start.png",
|
||||||
|
"target": "_blank"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Host Stats",
|
||||||
|
"href": "/stat.html",
|
||||||
|
"app": "stat"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Webstat</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<script src="stat.mjs" type="module"></script>
|
||||||
|
<style>
|
||||||
|
#pie {
|
||||||
|
max-width: 50px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="pie"></div>
|
||||||
|
<div id="chart"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -7,16 +7,16 @@ const Minute = 60 * Second
|
||||||
class App {
|
class App {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.stat = new WebStat.Stat()
|
this.stat = new WebStat.Stat()
|
||||||
this.areaChart = new WebStat.AreaChart(document.querySelector("#chart"))
|
this.areaChart = new WebStat.AreaChart(document.querySelector("#chart"), this.stat)
|
||||||
this.pieChart = new WebStat.PieChart(document.querySelector("#pie"))
|
this.pieChart = new WebStat.PieChart(document.querySelector("#pie"), this.stat)
|
||||||
|
|
||||||
setInterval(()=>this.update(), 2 * Second)
|
setInterval(()=>this.update(), 2 * Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
async update() {
|
async update() {
|
||||||
this.stat.update()
|
this.stat.update()
|
||||||
this.areaChart.update(this.stat)
|
this.areaChart.update()
|
||||||
this.pieChart.update(this.stat)
|
this.pieChart.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
//import Chart from "https://esm.run/chart.js@4.2.1/auto"
|
|
||||||
import Chart from "https://cdn.jsdelivr.net/npm/chart.js@4.2.1/auto/+esm"
|
import Chart from "https://cdn.jsdelivr.net/npm/chart.js@4.2.1/auto/+esm"
|
||||||
|
|
||||||
function qpush(arr, val, len) {
|
function qpush(arr, val, len) {
|
||||||
|
@ -53,7 +52,8 @@ class Stat {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AreaChart {
|
class AreaChart {
|
||||||
constructor(element, width=60) {
|
constructor(element, stat, width=60) {
|
||||||
|
this.stat = stat
|
||||||
this.width = width
|
this.width = width
|
||||||
this.canvas = element.appendChild(document.createElement("canvas"))
|
this.canvas = element.appendChild(document.createElement("canvas"))
|
||||||
this.data ={
|
this.data ={
|
||||||
|
@ -93,10 +93,9 @@ class AreaChart {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(stat) {
|
async update() {
|
||||||
let now = stat.Date
|
let cpu = this.stat.cpu()
|
||||||
let cpu = stat.cpu()
|
qpush(this.data.labels, this.stat.Date, this.width)
|
||||||
qpush(this.data.labels, now, this.width)
|
|
||||||
qpush(this.datasets.user, cpu.user, this.width)
|
qpush(this.datasets.user, cpu.user, this.width)
|
||||||
qpush(this.datasets.nice, cpu.nice, this.width)
|
qpush(this.datasets.nice, cpu.nice, this.width)
|
||||||
qpush(this.datasets.sys, cpu.sys, this.width)
|
qpush(this.datasets.sys, cpu.sys, this.width)
|
||||||
|
@ -107,7 +106,8 @@ class AreaChart {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PieChart {
|
class PieChart {
|
||||||
constructor(element) {
|
constructor(element, stat) {
|
||||||
|
this.stat = stat
|
||||||
this.canvas = element.appendChild(document.createElement("canvas"))
|
this.canvas = element.appendChild(document.createElement("canvas"))
|
||||||
this.data ={
|
this.data ={
|
||||||
labels: ["user", "nice", "sys", "idle", "wait"],
|
labels: ["user", "nice", "sys", "idle", "wait"],
|
||||||
|
@ -122,11 +122,11 @@ class PieChart {
|
||||||
animation: false,
|
animation: false,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
backgroundColor: [
|
backgroundColor: [
|
||||||
|
"blue",
|
||||||
"red",
|
"red",
|
||||||
"green",
|
|
||||||
"orange",
|
"orange",
|
||||||
"rgba(0, 64, 0, 0.2)",
|
"rgba(255, 255, 0, 0.2)",
|
||||||
"magenta",
|
"cyan",
|
||||||
],
|
],
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false },
|
legend: { display: false },
|
||||||
|
@ -136,8 +136,8 @@ class PieChart {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(stat) {
|
async update() {
|
||||||
let cpu = stat.cpu()
|
let cpu = this.stat.cpu()
|
||||||
this.data.datasets = [{
|
this.data.datasets = [{
|
||||||
label: "Current",
|
label: "Current",
|
||||||
data: [cpu.user, cpu.nice, cpu.sys, cpu.idle, cpu.wait],
|
data: [cpu.user, cpu.nice, cpu.sys, cpu.idle, cpu.wait],
|
||||||
|
|
Loading…
Reference in New Issue