Add Web Dashboard
This commit is contained in:
149
static/index.html
Normal file
149
static/index.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Ignis Control Center</title>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body { background: #0f172a; color: #f8fafc; font-family: 'Inter', sans-serif; }
|
||||
.card { background: #1e293b; border: 1px solid #334155; transition: transform 0.2s; }
|
||||
.card:hover { transform: translateY(-2px); }
|
||||
input[type="range"] { -webkit-appearance: none; height: 8px; border-radius: 4px; }
|
||||
input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 18px; height: 18px; background: white; border-radius: 50%; cursor: pointer; border: 2px solid #f97316; }
|
||||
.temp-gradient { background: linear-gradient(to right, #ffb366, #ffffff, #99ccff); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="p-4 md:p-8 max-w-5xl mx-auto">
|
||||
<header class="mb-10 flex flex-col md:flex-row justify-between items-center gap-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class="text-4xl">🔥</span>
|
||||
<h1 class="text-3xl font-black text-orange-500 tracking-tighter uppercase">Ignis Core</h1>
|
||||
</div>
|
||||
<div class="flex items-center gap-6 bg-slate-800/50 px-6 py-3 rounded-2xl border border-slate-700">
|
||||
<div class="text-center">
|
||||
<div class="text-xs text-slate-400 uppercase font-bold">Устройств</div>
|
||||
<div class="text-xl font-mono text-orange-400">{{ devicesCount }}</div>
|
||||
</div>
|
||||
<div class="h-8 w-[1px] bg-slate-700"></div>
|
||||
<button @click="allOff" class="text-xs bg-red-500/10 hover:bg-red-500/20 text-red-400 px-4 py-2 rounded-lg border border-red-500/20 transition-all">
|
||||
ВЫКЛЮЧИТЬ ВСЁ
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div v-for="(group, id) in groups" :key="id" class="card p-6 rounded-3xl shadow-2xl relative overflow-hidden">
|
||||
<div class="flex justify-between items-center mb-8">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">{{ group.name }}</h2>
|
||||
<span class="text-[10px] text-slate-500 font-mono">{{ id }}</span>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button @click="toggleGroup(id, true)" class="bg-orange-600 hover:bg-orange-500 text-white px-5 py-2 rounded-xl text-sm font-bold transition-colors shadow-lg shadow-orange-900/20">ВКЛ</button>
|
||||
<button @click="toggleGroup(id, false)" class="bg-slate-700 hover:bg-slate-600 text-white px-5 py-2 rounded-xl text-sm font-bold transition-colors">ВЫКЛ</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div>
|
||||
<div class="flex justify-between mb-2">
|
||||
<label class="text-xs font-black text-slate-400 uppercase tracking-widest">Яркость</label>
|
||||
<span class="text-xs font-mono text-orange-500">{{ group.brightness || 100 }}%</span>
|
||||
</div>
|
||||
<input type="range" min="10" max="100" class="w-full bg-slate-800 accent-orange-500"
|
||||
v-model="group.brightness"
|
||||
@change="setBrightness(id, $event.target.value)">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="flex justify-between mb-2">
|
||||
<label class="text-xs font-black text-slate-400 uppercase tracking-widest">Температура</label>
|
||||
<span class="text-xs font-mono text-blue-400">{{ group.temp || 3000 }}K</span>
|
||||
</div>
|
||||
<input type="range" min="2700" max="6500" step="100" class="w-full temp-gradient accent-white"
|
||||
v-model="group.temp"
|
||||
@change="setTemp(id, $event.target.value)">
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 items-end">
|
||||
<div class="w-full">
|
||||
<label class="text-xs font-black text-slate-400 uppercase tracking-widest mb-2 block">Цвет RGB</label>
|
||||
<input type="color" class="w-full h-12 bg-transparent border-2 border-slate-700 rounded-xl cursor-pointer"
|
||||
@input="setColor(id, $event.target.value)">
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button v-for="s in ['ocean', 'fireplace', 'party', 'steampunk']"
|
||||
@click="setScene(id, s)"
|
||||
class="flex-1 text-[9px] font-bold uppercase py-2 px-1 rounded-lg border border-slate-700 hover:bg-slate-800 transition-all">
|
||||
{{ s }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="Object.keys(groups).length === 0" class="text-center py-20 bg-slate-800/20 rounded-3xl border-2 border-dashed border-slate-700">
|
||||
<p class="text-slate-500 uppercase tracking-widest font-bold">Группы не найдены в базе</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp } = Vue
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
groups: {},
|
||||
devicesCount: 0
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async fetchData() {
|
||||
try {
|
||||
const gResp = await fetch('/groups');
|
||||
const gData = await gResp.json();
|
||||
// Сохраняем значения ползунков, чтобы они не прыгали при обновлении
|
||||
for (let id in gData) {
|
||||
if (this.groups[id]) {
|
||||
gData[id].brightness = this.groups[id].brightness;
|
||||
gData[id].temp = this.groups[id].temp;
|
||||
}
|
||||
}
|
||||
this.groups = gData;
|
||||
|
||||
const dResp = await fetch('/devices');
|
||||
const dData = await dResp.json();
|
||||
this.devicesCount = Object.keys(dData).length;
|
||||
} catch (e) { console.error("Ошибка обновления данных", e); }
|
||||
},
|
||||
async control(id, params) {
|
||||
const query = new URLSearchParams(params).toString();
|
||||
await fetch(`/control/group/${id}?${query}`, { method: 'POST' });
|
||||
},
|
||||
toggleGroup(id, state) { this.control(id, { state }); },
|
||||
setBrightness(id, val) { this.control(id, { brightness: val }); },
|
||||
setTemp(id, val) { this.control(id, { temp: val }); },
|
||||
setScene(id, scene) { this.control(id, { scene }); },
|
||||
setColor(id, hex) {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
this.control(id, { r, g, b });
|
||||
},
|
||||
async allOff() {
|
||||
for (let id in this.groups) {
|
||||
await this.toggleGroup(id, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
setInterval(this.fetchData, 10000); // Раз в 10 сек для статуса ламп
|
||||
}
|
||||
}).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user