Removed dashboard because I hate it
This commit is contained in:
parent
b3ff6f069c
commit
8f615e179e
187
dashboard.html
187
dashboard.html
@ -1,187 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>MQTT Device Dashboard (Nested Flash)</title>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
|
||||
<style>
|
||||
body { font-family: sans-serif; margin: 1rem; }
|
||||
.device { border: 1px solid #ccc; padding: 0.5rem; margin: 0.5rem; cursor: pointer; }
|
||||
.selected { background: #eef; }
|
||||
.panel { display: flex; gap: 1rem; }
|
||||
.left, .right { flex: 1; }
|
||||
.tree ul { list-style: none; padding-left: 1rem; margin: 0; }
|
||||
.tree li { margin: 2px 0; }
|
||||
.tree span { cursor: pointer; user-select: none; }
|
||||
.flash {
|
||||
animation: flash-bg 1s ease;
|
||||
}
|
||||
@keyframes flash-bg {
|
||||
0% { background-color: yellow; }
|
||||
100% { background-color: transparent; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="app">
|
||||
<h1>Device Dashboard</h1>
|
||||
<div class="panel">
|
||||
<div class="left">
|
||||
<h2>Devices</h2>
|
||||
<div v-for="(device, id) in devices"
|
||||
:key="id"
|
||||
@click="selected = id"
|
||||
:class="['device', {selected: selected===id}]">
|
||||
{{ id }} — {{ device.status }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right" v-if="selected">
|
||||
<h2>Properties & Commands: {{ selected }}</h2>
|
||||
|
||||
<div>
|
||||
<h3>Properties</h3>
|
||||
<div class="tree">
|
||||
<tree-view :data="devices[selected].properties"
|
||||
:flash-map="flashMap"></tree-view>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Commands</h3>
|
||||
<ul>
|
||||
<li v-for="(cmd, name) in devices[selected].commands" :key="name">
|
||||
{{ name }} — schema: {{ JSON.stringify(cmd.schema) }}
|
||||
<button @click="sendCommand(selected, name)">Send</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp } = Vue;
|
||||
|
||||
// Recursive tree component
|
||||
const TreeView = {
|
||||
name: "TreeView",
|
||||
props: ["data", "flashMap", "basePath"],
|
||||
data() { return { openNodes: new Set() }; },
|
||||
methods: {
|
||||
toggle(key) {
|
||||
if (this.openNodes.has(key)) this.openNodes.delete(key);
|
||||
else this.openNodes.add(key);
|
||||
},
|
||||
isObject(val) { return val && typeof val === "object" && !Array.isArray(val); },
|
||||
getFlashClass(fullPath) {
|
||||
return this.flashMap[fullPath] ? "flash" : "";
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<ul>
|
||||
<li v-for="(val, key) in data" :key="key">
|
||||
<span v-if="isObject(val)"
|
||||
@click="toggle(key)"
|
||||
:class="getFlashClass(basePath ? basePath + '.' + key : key)">
|
||||
{{ key }} {{ openNodes.has(key) ? '▼' : '▶' }}
|
||||
</span>
|
||||
<span v-else :class="getFlashClass(basePath ? basePath + '.' + key : key)">
|
||||
{{ key }}: {{ val }}
|
||||
</span>
|
||||
<tree-view
|
||||
v-if="isObject(val) && openNodes.has(key)"
|
||||
:data="val"
|
||||
:flash-map="flashMap"
|
||||
:base-path="basePath ? basePath + '.' + key : key">
|
||||
</tree-view>
|
||||
</li>
|
||||
</ul>
|
||||
`,
|
||||
components: { TreeView: null },
|
||||
created() { this.$options.components.TreeView = TreeView; }
|
||||
};
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
devices: {},
|
||||
selected: null,
|
||||
client: null,
|
||||
flashMap: {} // maps "full.path" => boolean
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const clientID = "webui-" + Math.random().toString(16).substr(2, 8);
|
||||
this.client = mqtt.connect("ws://localhost:8083/mqtt", { clientId: clientID });
|
||||
|
||||
this.client.on("connect", () => {
|
||||
console.log("Connected to broker");
|
||||
this.client.subscribe("asset/+/status");
|
||||
this.client.subscribe("asset/+/property/#");
|
||||
this.client.subscribe("asset/+/command/+");
|
||||
});
|
||||
|
||||
this.client.on("message", (topic, payloadBuffer) => {
|
||||
const payload = payloadBuffer.toString();
|
||||
const parts = topic.split("/");
|
||||
|
||||
if (parts[0] !== "asset") return;
|
||||
const deviceID = parts[1];
|
||||
const category = parts[2];
|
||||
const keyPath = parts.slice(3);
|
||||
|
||||
if (!this.devices[deviceID]) {
|
||||
this.devices[deviceID] = { status: "OFFLINE", properties: {}, commands: {} };
|
||||
}
|
||||
const device = this.devices[deviceID];
|
||||
|
||||
if (category === "status") {
|
||||
device.status = payload;
|
||||
} else if (category === "property") {
|
||||
let target = device.properties;
|
||||
for (let i = 0; i < keyPath.length - 1; i++) {
|
||||
const k = keyPath[i];
|
||||
if (!target[k] || typeof target[k] !== "object") target[k] = {};
|
||||
target = target[k];
|
||||
}
|
||||
|
||||
const leafKey = keyPath[keyPath.length - 1];
|
||||
let value;
|
||||
try { value = JSON.parse(payload); }
|
||||
catch(e) { value = payload; }
|
||||
|
||||
target[leafKey] = value;
|
||||
|
||||
// generate all ancestor paths for flashing
|
||||
const paths = [];
|
||||
for (let i = 0; i < keyPath.length; i++) {
|
||||
paths.push(keyPath.slice(0, i + 1).join("."));
|
||||
}
|
||||
paths.forEach(p => {
|
||||
this.flashMap[p] = true;
|
||||
setTimeout(() => { this.flashMap[p] = false; }, 1000);
|
||||
});
|
||||
|
||||
} else if (category === "command" && keyPath.length === 1) {
|
||||
const cmdName = keyPath[0];
|
||||
try { device.commands[cmdName] = JSON.parse(payload); }
|
||||
catch(e) { device.commands[cmdName] = { raw: payload }; }
|
||||
}
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
sendCommand(deviceID, commandName) {
|
||||
const topic = `asset/${deviceID}/command/${commandName}`;
|
||||
const payload = JSON.stringify({ args: {} });
|
||||
this.client.publish(topic, payload);
|
||||
console.log("Sent command", topic, payload);
|
||||
}
|
||||
},
|
||||
components: { TreeView }
|
||||
}).mount("#app");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user