diff --git a/internal/service/api/ui/iot-console.html b/internal/service/api/ui/iot-console.html index f459310..cc9668a 100644 --- a/internal/service/api/ui/iot-console.html +++ b/internal/service/api/ui/iot-console.html @@ -6,6 +6,7 @@ Функционал: вход, список устройств, credentials, эмулятор MQTT, заглушка телеметрии. Встраивается в iot-service через go:embed, раздаётся по GET /console. --> +
@@ -23,569 +24,929 @@ #6b8eaa — text secondary #2d5070 — text muted / disabled */ - *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + *, + *::before, + *::after { + box-sizing: border-box; + margin: 0; + padding: 0; + } + body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; - background: #001120; color: #e2ecf6; min-height: 100vh; + background: #001120; + color: #e2ecf6; + min-height: 100vh; } /* ——— Navbar ——— */ .navbar { background: #001C34; border-bottom: 1px solid #0b2d50; - padding: 0 28px; height: 58px; - display: flex; align-items: center; gap: 14px; + padding: 0 28px; + height: 58px; + display: flex; + align-items: center; + gap: 14px; } + /* Логотип Nubes — SVG с инвертированием для белого цвета на тёмном фоне */ .navbar-logo { - display: flex; align-items: center; gap: 10px; - cursor: pointer; user-select: none; text-decoration: none; + display: flex; + align-items: center; + gap: 10px; + cursor: pointer; + user-select: none; + text-decoration: none; } + .navbar-logo img { height: 18px; /* SVG залит #001C34, инвертируем до белого */ filter: brightness(0) invert(1); } + .navbar-logo-sep { - width: 1px; height: 18px; background: #1a4a73; margin: 0 4px; + 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; + font-size: 13px; + font-weight: 600; + color: #7aafd4; + letter-spacing: 0.04em; + text-transform: uppercase; + } + + .navbar-spacer { + flex: 1; } - .navbar-spacer { flex: 1; } /* ——— Layout ——— */ - .container { max-width: 940px; margin: 0 auto; padding: 32px 24px; } + .container { + max-width: 940px; + margin: 0 auto; + padding: 32px 24px; + } /* ——— Card ——— */ .card { background: #001929; border-radius: 8px; border: 1px solid #0b2d50; - padding: 24px; margin-bottom: 16px; + padding: 24px; + margin-bottom: 16px; } + .card-title { - font-size: 15px; font-weight: 600; + font-size: 15px; + font-weight: 600; color: #b8d4ec; margin-bottom: 16px; letter-spacing: 0.01em; } /* ——— 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 { + .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; + border-radius: 5px; + padding: 10px 12px; color: #e2ecf6; - font-size: 14px; font-family: inherit; + 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; } + + 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 ——— */ .btn { - 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; + 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.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; } + + .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 { width: 100%; border-collapse: collapse; } - th { - 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; + table { + width: 100%; + border-collapse: collapse; + } + + th { + 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(11, 45, 80, 0.6); + vertical-align: middle; + } + + tbody tr:last-child td { + border-bottom: none; + } + + tbody tr:hover td { + background: rgba(26, 127, 212, 0.04); } - 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(26,127,212,0.04); } /* ——— Badges ——— */ .badge { - display: inline-flex; align-items: center; gap: 5px; - font-size: 11px; font-weight: 600; border-radius: 4px; - padding: 3px 9px; letter-spacing: 0.04em; text-transform: uppercase; + display: inline-flex; + align-items: center; + gap: 5px; + font-size: 11px; + font-weight: 600; + border-radius: 4px; + padding: 3px 9px; + letter-spacing: 0.04em; + text-transform: uppercase; + } + + .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; } - .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 { - display: flex; border-bottom: 1px solid #0b2d50; margin-bottom: 24px; + display: flex; + border-bottom: 1px solid #0b2d50; + margin-bottom: 24px; } + .tab { - padding: 11px 20px; font-size: 13px; font-weight: 600; - cursor: pointer; color: #2d5070; + 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; + user-select: none; + letter-spacing: 0.03em; + } + + .tab:hover { + color: #7aafd4; + } + + .tab.active { + color: #1a7fd4; + border-bottom-color: #1a7fd4; } - .tab:hover { color: #7aafd4; } - .tab.active { color: #1a7fd4; border-bottom-color: #1a7fd4; } /* ——— Credentials ——— */ - .cred-row { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; } - .cred-label { font-size: 13px; font-weight: 600; color: #7eb8e0; min-width: 110px; } + .cred-row { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 14px; + } + + .cred-label { + font-size: 13px; + font-weight: 600; + color: #7eb8e0; + min-width: 110px; + } + .cred-value { - font-family: 'Courier New', monospace; font-size: 14px; - background: #0a1e30; padding: 9px 14px; border-radius: 6px; - border: 1px solid #1a4a6e; flex: 1; color: #e2f0ff; - overflow-x: auto; word-break: break-all; + font-family: 'Courier New', monospace; + font-size: 14px; + background: #0a1e30; + padding: 9px 14px; + border-radius: 6px; + border: 1px solid #1a4a6e; + flex: 1; + color: #e2f0ff; + overflow-x: auto; + word-break: break-all; } /* ——— Connection pill ——— */ .conn-pill { - display: inline-flex; align-items: center; gap: 7px; - font-size: 12px; font-weight: 600; padding: 4px 13px; - border-radius: 4px; letter-spacing: 0.04em; text-transform: uppercase; + display: inline-flex; + align-items: center; + gap: 7px; + 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: 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; + } } - .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 { - 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; + 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: #2ecc8a; } - .mqtt-log .err { color: #e55042; } - .mqtt-log .info { color: #2d5070; } + + .mqtt-log .ok { + color: #2ecc8a; + } + + .mqtt-log .err { + color: #e55042; + } + + .mqtt-log .info { + color: #2d5070; + } /* ——— Modal ——— */ .modal-overlay { - position: fixed; inset: 0; background: rgba(0,8,16,0.82); - display: flex; align-items: center; justify-content: center; z-index: 200; + position: fixed; + inset: 0; + background: rgba(0, 8, 16, 0.82); + display: flex; + align-items: center; + justify-content: center; + z-index: 200; } + .modal { - background: #001929; border: 1px solid #0b2d50; border-radius: 8px; - padding: 28px; width: 100%; max-width: 460px; + background: #001929; + border: 1px solid #0b2d50; + border-radius: 8px; + padding: 28px; + width: 100%; + max-width: 460px; + } + + .modal-title { + font-size: 15px; + font-weight: 700; + margin-bottom: 20px; + color: #b8d4ec; } - .modal-title { font-size: 15px; font-weight: 700; margin-bottom: 20px; color: #b8d4ec; } /* ——— 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); } + .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 ——— */ - .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 #0b2d50; margin: 20px 0; } - .hint { font-size: 14px; color: #a0bcd8; line-height: 1.7; } - .mono { font-family: 'Courier New', monospace; } + .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 #0b2d50; + margin: 20px 0; + } + + .hint { + font-size: 14px; + color: #a0bcd8; + line-height: 1.7; + } + + .mono { + font-family: 'Courier New', monospace; + } /* ——— 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; } + .coming-soon { + text-align: center; + padding: 56px 24px; + color: #2d5070; + } - a { color: #1a7fd4; text-decoration: none; } - a:hover { text-decoration: underline; } + .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 ——— */ .help-block { - border: 1px solid #0b2d50; border-radius: 8px; - padding: 16px 20px; background: #001929; + border: 1px solid #0b2d50; + border-radius: 8px; + padding: 16px 20px; + background: #001929; } - .help-block summary::-webkit-details-marker { display: none; } - .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-block summary::-webkit-details-marker { + display: none; + } + + .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; + 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; } - .help-step strong { color: #b8d4ec; } - .help-step em { color: #1a7fd4; font-style: normal; } + - + - - + + - + // ============================================================= + // СТАРТ + // ============================================================= + render(); + - + +