design(iot-console): Nubes brand rebrand v0.1.57

- Палитра: #001C34 (Nubes navy) как фоновая карточек/navbar
- Логотип Nubes SVG в navbar и на экране входа (filter:invert → белый)
- Убраны эмодзи из brand-элементов
- Accent: #1a7fd4 (корпоративный синий на тёмном фоне)
- Badges: прямоугольные, UPPERCASE, строгие
- Кнопки/формы/таблицы: Nubes-спецификация
This commit is contained in:
Naeel
2026-04-04 20:34:23 +03:00
parent e54787177b
commit 0400f97eb6
2 changed files with 167 additions and 112 deletions
+166 -111
View File
@@ -1,188 +1,238 @@
<!DOCTYPE html>
<!-- Создано: 2026-04-04
Изменено: 2026-04-04 (design: Nubes brand, #001C34 palette, logo)
IoT Консоль — SPA для управления IoT устройствами.
Стек: ванильный HTML/CSS/JS + mqtt.js (CDN).
Функционал: вход, список устройств, credentials, эмулятор MQTT, заглушка телеметрии.
Встраивается в оператор через go:embed, раздаётся по GET /console.
Доступна по: http://iot.kube5s.ru/console -->
Доступна по: https://iot.kube5s.ru/console -->
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IoT Консоль</title>
<title>Nubes IoT Console</title>
<style>
/* ——— Nubes brand palette ———
#001C34 — Nubes navy (brand primary, navbar, logo)
#001120 — page background (deeper than brand)
#001929 — card surface
#0b2d50 — borders, dividers
#1a7fd4 — action accent (bright blue on navy)
#e2ecf6 — text primary
#6b8eaa — text secondary
#2d5070 — text muted / disabled */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
background: #0f172a; color: #e2e8f0; min-height: 100vh;
background: #001120; color: #e2ecf6; min-height: 100vh;
}
/* Navbar */
/* ——— Navbar ——— */
.navbar {
background: #1e293b; border-bottom: 1px solid #334155;
padding: 0 24px; height: 56px;
display: flex; align-items: center; gap: 12px;
background: #001C34;
border-bottom: 1px solid #0b2d50;
padding: 0 28px; height: 58px;
display: flex; align-items: center; gap: 14px;
}
.navbar-brand {
font-weight: 800; font-size: 17px; color: #3b82f6; cursor: pointer;
display: flex; align-items: center; gap: 6px; user-select: none;
/* Логотип Nubes — SVG с инвертированием для белого цвета на тёмном фоне */
.navbar-logo {
display: flex; align-items: center; gap: 10px;
cursor: pointer; user-select: none; text-decoration: none;
}
.navbar-ns {
background: #334155; border-radius: 4px; padding: 3px 10px;
font-size: 12px; color: #94a3b8; font-family: monospace;
.navbar-logo img {
height: 18px;
/* SVG залит #001C34, инвертируем до белого */
filter: brightness(0) invert(1);
}
.navbar-logo-sep {
width: 1px; height: 18px; background: #1a4a73; margin: 0 4px;
}
.navbar-logo-product {
font-size: 13px; font-weight: 600; color: #7aafd4;
letter-spacing: 0.04em; text-transform: uppercase;
}
.navbar-spacer { flex: 1; }
/* Layout */
.container { max-width: 920px; margin: 0 auto; padding: 32px 24px; }
/* ——— Layout ——— */
.container { max-width: 940px; margin: 0 auto; padding: 32px 24px; }
/* Card */
/* ——— Card ——— */
.card {
background: #1e293b; border-radius: 10px; border: 1px solid #334155;
background: #001929;
border-radius: 8px;
border: 1px solid #0b2d50;
padding: 24px; margin-bottom: 16px;
}
.card-title { font-size: 16px; font-weight: 600; margin-bottom: 16px; }
/* Form */
.form-group { margin-bottom: 16px; }
label { display: block; font-size: 13px; color: #94a3b8; margin-bottom: 6px; }
input, textarea, select {
width: 100%; background: #0f172a; border: 1px solid #334155;
border-radius: 6px; padding: 10px 12px; color: #e2e8f0;
font-size: 14px; font-family: inherit;
.card-title {
font-size: 15px; font-weight: 600;
color: #b8d4ec;
margin-bottom: 16px;
letter-spacing: 0.01em;
}
input:focus, textarea:focus { outline: none; border-color: #3b82f6; }
/* ——— Form ——— */
.form-group { margin-bottom: 16px; }
label { display: block; font-size: 12px; font-weight: 500; color: #6b8eaa; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 0.05em; }
input, textarea, select {
width: 100%;
background: #001120;
border: 1px solid #0b2d50;
border-radius: 5px; padding: 10px 12px;
color: #e2ecf6;
font-size: 14px; font-family: inherit;
transition: border-color 0.15s;
}
input:focus, textarea:focus { outline: none; border-color: #1a7fd4; box-shadow: 0 0 0 2px rgba(26,127,212,0.15); }
textarea { resize: vertical; font-family: 'Courier New', monospace; line-height: 1.5; }
input[type="number"] { width: auto; }
/* Buttons */
/* ——— Buttons ——— */
.btn {
border: none; border-radius: 6px; padding: 10px 18px; font-size: 14px;
font-weight: 500; cursor: pointer; transition: opacity 0.15s; display: inline-flex;
align-items: center; gap: 6px; white-space: nowrap;
border: none; border-radius: 5px; padding: 10px 18px; font-size: 13px;
font-weight: 600; cursor: pointer; transition: opacity 0.15s, background 0.15s;
display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
letter-spacing: 0.02em;
}
.btn:hover:not(:disabled) { opacity: 0.82; }
.btn:disabled { opacity: 0.38; cursor: not-allowed; }
.btn-primary { background: #3b82f6; color: #fff; }
.btn-danger { background: #ef4444; color: #fff; }
.btn-success { background: #22c55e; color: #0f172a; font-weight: 600; }
.btn-warning { background: #f59e0b; color: #0f172a; font-weight: 600; }
.btn-ghost { background: transparent; color: #94a3b8; border: 1px solid #334155; }
.btn-sm { padding: 5px 12px; font-size: 12px; }
.btn:hover:not(:disabled) { opacity: 0.85; }
.btn:disabled { opacity: 0.35; cursor: not-allowed; }
.btn-primary { background: #1a7fd4; color: #fff; }
.btn-danger { background: #c0392b; color: #fff; }
.btn-success { background: #1a9e5c; color: #fff; }
.btn-warning { background: #d48f0a; color: #fff; }
.btn-ghost { background: transparent; color: #6b8eaa; border: 1px solid #0b2d50; }
.btn-ghost:hover:not(:disabled) { color: #b8d4ec; border-color: #1a4a73; opacity: 1; }
.btn-sm { padding: 5px 11px; font-size: 12px; }
/* Table */
/* ——— Table ——— */
table { width: 100%; border-collapse: collapse; }
th {
text-align: left; font-size: 11px; color: #64748b;
text-transform: uppercase; letter-spacing: 0.06em;
padding: 0 14px 10px; border-bottom: 1px solid #334155;
text-align: left; font-size: 11px; font-weight: 600; color: #2d5070;
text-transform: uppercase; letter-spacing: 0.07em;
padding: 0 14px 10px; border-bottom: 1px solid #0b2d50;
}
td { padding: 13px 14px; border-bottom: 1px solid rgba(51,65,85,0.5); vertical-align: middle; }
td { padding: 13px 14px; border-bottom: 1px solid rgba(11,45,80,0.6); vertical-align: middle; }
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: rgba(255,255,255,0.02); }
tbody tr:hover td { background: rgba(26,127,212,0.04); }
/* Badges */
/* ——— Badges ——— */
.badge {
display: inline-flex; align-items: center; gap: 5px;
font-size: 12px; font-weight: 500; border-radius: 9999px; padding: 2px 10px;
font-size: 11px; font-weight: 600; border-radius: 4px;
padding: 3px 9px; letter-spacing: 0.04em; text-transform: uppercase;
}
.badge::before { content: '●'; font-size: 7px; }
.badge-active { background: rgba(34,197,94,0.12); color: #22c55e; }
.badge-active::before { color: #22c55e; }
.badge-disabled { background: rgba(100,116,139,0.12); color: #64748b; }
.badge-disabled::before { color: #64748b; }
.badge-error { background: rgba(239,68,68,0.12); color: #ef4444; }
.badge-error::before { color: #ef4444; }
.badge-pending { background: rgba(251,191,36,0.12); color: #fbbf24; }
.badge-pending::before { color: #fbbf24; }
.badge::before { content: '●'; font-size: 6px; }
.badge-active { background: rgba(26,158,92,0.15); color: #2ecc8a; }
.badge-active::before { color: #2ecc8a; }
.badge-disabled { background: rgba(45,80,112,0.3); color: #2d5070; }
.badge-disabled::before { color: #2d5070; }
.badge-error { background: rgba(192,57,43,0.15); color: #e55042; }
.badge-error::before { color: #e55042; }
.badge-pending { background: rgba(212,143,10,0.15); color: #d4a017; }
.badge-pending::before { color: #d4a017; }
/* Tabs */
/* ——— Tabs ——— */
.tabs {
display: flex; border-bottom: 1px solid #334155; margin-bottom: 24px;
display: flex; border-bottom: 1px solid #0b2d50; margin-bottom: 24px;
}
.tab {
padding: 12px 20px; font-size: 14px; font-weight: 500; cursor: pointer;
color: #64748b; border-bottom: 2px solid transparent; transition: all 0.15s;
user-select: none;
padding: 11px 20px; font-size: 13px; font-weight: 600;
cursor: pointer; color: #2d5070;
border-bottom: 2px solid transparent;
transition: color 0.15s, border-color 0.15s;
user-select: none; letter-spacing: 0.03em;
}
.tab:hover { color: #cbd5e1; }
.tab.active { color: #3b82f6; border-bottom-color: #3b82f6; }
.tab:hover { color: #7aafd4; }
.tab.active { color: #1a7fd4; border-bottom-color: #1a7fd4; }
/* Credentials rows */
/* ——— Credentials ——— */
.cred-row { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.cred-label { font-size: 12px; color: #64748b; min-width: 130px; }
.cred-label { font-size: 11px; font-weight: 600; color: #2d5070; min-width: 130px; text-transform: uppercase; letter-spacing: 0.05em; }
.cred-value {
font-family: 'Courier New', monospace; font-size: 13px;
background: #0a0f1a; padding: 8px 12px; border-radius: 4px;
border: 1px solid #334155; flex: 1; color: #cbd5e1;
background: #000d18; padding: 8px 12px; border-radius: 4px;
border: 1px solid #0b2d50; flex: 1; color: #9ecaee;
overflow-x: auto; word-break: break-all;
}
/* Connection status pill */
/* ——— Connection pill ——— */
.conn-pill {
display: inline-flex; align-items: center; gap: 7px;
font-size: 13px; font-weight: 500; padding: 5px 14px; border-radius: 9999px;
font-size: 12px; font-weight: 600; padding: 4px 13px;
border-radius: 4px; letter-spacing: 0.04em; text-transform: uppercase;
}
.conn-pill::before { content: '●'; font-size: 7px; }
.conn-pill.off { background: rgba(100,116,139,0.12); color: #64748b; }
.conn-pill.off::before { color: #64748b; }
.conn-pill.ing { background: rgba(251,191,36,0.12); color: #fbbf24; }
.conn-pill.ing::before { color: #fbbf24; animation: blink 1s step-start infinite; }
.conn-pill.on { background: rgba(34,197,94,0.12); color: #22c55e; }
.conn-pill.on::before { color: #22c55e; }
.conn-pill::before { content: '●'; font-size: 6px; }
.conn-pill.off { background: rgba(45,80,112,0.25); color: #2d5070; }
.conn-pill.off::before { color: #2d5070; }
.conn-pill.ing { background: rgba(212,143,10,0.15); color: #d4a017; }
.conn-pill.ing::before { color: #d4a017; animation: blink 1s step-start infinite; }
.conn-pill.on { background: rgba(26,158,92,0.15); color: #2ecc8a; }
.conn-pill.on::before { color: #2ecc8a; }
@keyframes blink { 50% { opacity: 0; } }
/* MQTT log */
/* ——— MQTT log ——— */
.mqtt-log {
background: #080d18; border: 1px solid #334155; border-radius: 6px;
background: #000d18; border: 1px solid #0b2d50; border-radius: 5px;
padding: 10px 12px; max-height: 180px; overflow-y: auto;
font-family: 'Courier New', monospace; font-size: 12px;
line-height: 1.6;
}
.mqtt-log .ok { color: #22c55e; }
.mqtt-log .err { color: #ef4444; }
.mqtt-log .info { color: #64748b; }
.mqtt-log .ok { color: #2ecc8a; }
.mqtt-log .err { color: #e55042; }
.mqtt-log .info { color: #2d5070; }
/* Modal */
/* ——— Modal ——— */
.modal-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.72);
position: fixed; inset: 0; background: rgba(0,8,16,0.82);
display: flex; align-items: center; justify-content: center; z-index: 200;
}
.modal {
background: #1e293b; border: 1px solid #334155; border-radius: 12px;
background: #001929; border: 1px solid #0b2d50; border-radius: 8px;
padding: 28px; width: 100%; max-width: 460px;
}
.modal-title { font-size: 16px; font-weight: 600; margin-bottom: 20px; }
.modal-title { font-size: 15px; font-weight: 700; margin-bottom: 20px; color: #b8d4ec; }
/* Alerts */
.alert { padding: 11px 15px; border-radius: 6px; font-size: 13px; margin-bottom: 14px; }
.alert-error { background: rgba(239,68,68,0.12); color: #f87171; border: 1px solid rgba(239,68,68,0.2); }
.alert-success { background: rgba(34,197,94,0.12); color: #4ade80; border: 1px solid rgba(34,197,94,0.2); }
.alert-info { background: rgba(59,130,246,0.12); color: #60a5fa; border: 1px solid rgba(59,130,246,0.2); }
/* ——— Alerts ——— */
.alert { padding: 11px 15px; border-radius: 5px; font-size: 13px; margin-bottom: 14px; }
.alert-error { background: rgba(192,57,43,0.1); color: #e55042; border: 1px solid rgba(192,57,43,0.25); }
.alert-success { background: rgba(26,158,92,0.1); color: #2ecc8a; border: 1px solid rgba(26,158,92,0.25); }
.alert-info { background: rgba(26,127,212,0.1); color: #5aaae8; border: 1px solid rgba(26,127,212,0.25); }
/* Misc */
/* ——— Misc ——— */
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.flex1 { flex: 1; }
.mt8 { margin-top: 8px; }
.mt16 { margin-top: 16px; }
.mt24 { margin-top: 24px; }
.separator { border: none; border-top: 1px solid #334155; margin: 20px 0; }
.hint { font-size: 12px; color: #64748b; line-height: 1.6; }
.separator { border: none; border-top: 1px solid #0b2d50; margin: 20px 0; }
.hint { font-size: 12px; color: #2d5070; line-height: 1.6; }
.mono { font-family: 'Courier New', monospace; }
.coming-soon {
text-align: center; padding: 56px 24px; color: #475569;
}
.coming-soon .icon { font-size: 40px; margin-bottom: 12px; }
.coming-soon h3 { font-size: 18px; font-weight: 600; margin-bottom: 8px; color: #64748b; }
a { color: #3b82f6; text-decoration: none; }
/* ——— Device list empty state ——— */
.coming-soon { text-align: center; padding: 56px 24px; color: #2d5070; }
.coming-soon .icon { font-size: 36px; margin-bottom: 12px; opacity: 0.5; }
.coming-soon h3 { font-size: 17px; font-weight: 600; margin-bottom: 8px; color: #1a4a73; }
a { color: #1a7fd4; text-decoration: none; }
a:hover { text-decoration: underline; }
.help-block { border: 1px solid #1e293b; border-radius: 10px; padding: 14px 18px; background: #0f172a; }
/* ——— Help block ——— */
.help-block {
border: 1px solid #0b2d50; border-radius: 8px;
padding: 16px 20px; background: #001929;
}
.help-block summary::-webkit-details-marker { display: none; }
.help-block[open] summary span { transform: rotate(90deg); display: inline-block; }
.help-step { display: flex; gap: 14px; align-items: flex-start; font-size: 13px; color: #94a3b8; line-height: 1.6; }
.help-num { flex-shrink: 0; width: 24px; height: 24px; border-radius: 50%; background: #1e3a5f; color: #60a5fa; font-weight: 700; font-size: 12px; display: flex; align-items: center; justify-content: center; margin-top: 2px; }
.help-step strong { color: #e2e8f0; }
.help-step em { color: #60a5fa; font-style: normal; }
.help-block[open] summary .help-arrow { transform: rotate(90deg); }
.help-arrow { display: inline-block; transition: transform 0.15s; margin-right: 6px; font-size: 10px; }
.help-step { display: flex; gap: 14px; align-items: flex-start; font-size: 13px; color: #6b8eaa; line-height: 1.6; }
.help-num {
flex-shrink: 0; width: 22px; height: 22px; border-radius: 4px;
background: #001C34; border: 1px solid #1a4a73;
color: #1a7fd4; font-weight: 700; font-size: 11px;
display: flex; align-items: center; justify-content: center; margin-top: 2px;
}
.help-step strong { color: #b8d4ec; }
.help-step em { color: #1a7fd4; font-style: normal; }
</style>
</head>
<body>
@@ -474,10 +524,11 @@ function renderLogin() {
app().innerHTML = `
<div style="display:flex;align-items:center;justify-content:center;min-height:100vh;padding:24px;">
<div class="card" style="width:100%;max-width:420px;">
<div style="text-align:center;margin-bottom:28px;">
<div style="font-size:32px;margin-bottom:8px;">⚡</div>
<div style="font-size:22px;font-weight:800;color:#3b82f6;">IoT Консоль</div>
<div style="font-size:13px;color:#64748b;margin-top:4px;">Управление устройствами и телеметрией</div>
<div style="text-align:center;margin-bottom:32px;">
<img src="https://terra.k8c.ru/docs/nubes/nubes/2.0.2/30_registry/assets/logo.svg"
alt="Nubes" style="height:24px;filter:brightness(0) invert(1);margin-bottom:16px;">
<div style="font-size:11px;font-weight:700;color:#1a7fd4;letter-spacing:0.18em;text-transform:uppercase;margin-bottom:6px;">IoT Console</div>
<div style="font-size:12px;color:#2d5070;">Управление устройствами и телеметрией</div>
</div>
<div id="login-err"></div>
<div class="form-group">
@@ -723,7 +774,7 @@ function deviceLayout(d) {
<details class="help-block" style="margin-top:24px;">
<summary style="cursor:pointer;font-size:13px;font-weight:600;color:#64748b;user-select:none;list-style:none;display:flex;align-items:center;gap:6px;">
<span>▶</span> Как это работает — краткая инструкция
<span class="help-arrow">▶</span> Как это работает — краткая инструкция
</summary>
<div style="margin-top:14px;display:grid;gap:12px;">
@@ -1010,7 +1061,11 @@ function telemetryTab() {
function navbar() {
return `
<nav class="navbar">
<div class="navbar-brand" onclick="nav('devices')">⚡ IoT Консоль</div>
<div class="navbar-logo" onclick="nav('devices')">
<img src="https://terra.k8c.ru/docs/nubes/nubes/2.0.2/30_registry/assets/logo.svg" alt="Nubes">
<div class="navbar-logo-sep"></div>
<span class="navbar-logo-product">IoT Console</span>
</div>
<div class="navbar-spacer"></div>
<button class="btn btn-ghost btn-sm" onclick="doLogout()">Выйти</button>
</nav>`;