Files
ignis-core/static/index.html
Артём Кокос 87e03fdb26 Add Web Dashboard
2026-02-12 23:17:13 +07:00

150 lines
8.0 KiB
HTML

<!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>