Files
shd-modules-mcp-plugin/plugins/shd-mcp-plugin/widgets/controller-observability/source.html
T

1572 lines
70 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SHD controller observability</title>
<style data-shd-widget-theme="shared">
:root {
color-scheme: light dark;
--shd-bg: #f3f6f9;
--shd-surface: #ffffff;
--shd-surface-raised: #ffffff;
--shd-surface-subtle: #eef2f6;
--shd-line: #d6dee7;
--shd-line-strong: #b9c5d1;
--shd-ink: #17202a;
--shd-muted: #66717e;
--shd-accent: #496581;
--shd-accent-strong: #35516d;
--shd-accent-soft: rgba(73, 101, 129, .12);
--shd-on-accent: #ffffff;
--shd-good: #19734b;
--shd-good-soft: #e2f3eb;
--shd-warning: #946000;
--shd-warning-soft: #fff2cf;
--shd-danger: #b42318;
--shd-danger-soft: #ffebe9;
--shd-shadow: 0 1px 2px rgba(15, 23, 42, .05), 0 12px 30px rgba(15, 23, 42, .08);
--shd-shadow-soft: 0 1px 2px rgba(15, 23, 42, .05);
--shd-radius-sm: 10px;
--shd-radius-md: 14px;
--shd-radius-lg: 20px;
--shd-space-1: 4px;
--shd-space-2: 8px;
--shd-space-3: 12px;
--shd-space-4: 16px;
--shd-space-5: 20px;
--shd-space-6: 24px;
--shd-space-7: 32px;
/* Compatibility aliases for existing widget renderers and write actions. */
--bg: var(--shd-bg);
--surface: var(--shd-surface);
--panel: var(--shd-surface);
--hover: var(--shd-surface-subtle);
--surface-hover: var(--shd-surface-subtle);
--line: var(--shd-line);
--border: var(--shd-line);
--ink: var(--shd-ink);
--text: var(--shd-ink);
--muted: var(--shd-muted);
--accent: var(--shd-accent);
--soft: var(--shd-accent-soft);
--accent-soft: var(--shd-accent-soft);
--good: var(--shd-good);
--success: var(--shd-good);
--success-soft: var(--shd-good-soft);
--warning: var(--shd-warning);
--warning-soft: var(--shd-warning-soft);
--bad: var(--shd-danger);
--danger: var(--shd-danger);
--danger-soft: var(--shd-danger-soft);
--button: var(--shd-on-accent);
--shadow: var(--shd-shadow);
}
@media (prefers-color-scheme: dark) {
:root {
--shd-bg: #15181c;
--shd-surface: #1e2328;
--shd-surface-raised: #262c32;
--shd-surface-subtle: #2d343b;
--shd-line: #3d464f;
--shd-line-strong: #56616c;
--shd-ink: #f1f4f7;
--shd-muted: #a5adb6;
--shd-accent: #d8b56a;
--shd-accent-strong: #edc97d;
--shd-accent-soft: rgba(216, 181, 106, .16);
--shd-on-accent: #211b10;
--shd-good: #83d2a4;
--shd-good-soft: rgba(71, 164, 111, .22);
--shd-warning: #f2c66d;
--shd-warning-soft: rgba(196, 139, 40, .22);
--shd-danger: #ff9a93;
--shd-danger-soft: rgba(196, 61, 50, .22);
--shd-shadow: 0 1px 2px rgba(0, 0, 0, .22), 0 16px 34px rgba(0, 0, 0, .24);
--shd-shadow-soft: 0 1px 2px rgba(0, 0, 0, .22);
}
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
min-width: 0;
background: var(--shd-bg);
}
body {
min-width: 0;
margin: 0;
background: var(--shd-bg);
color: var(--shd-ink);
font: 14px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
button,
input,
select,
textarea {
font: inherit;
}
button {
color: inherit;
}
.shell,
.app {
width: 100%;
max-width: 1240px;
margin: 0 auto;
padding: clamp(10px, 1.5vw, 18px);
}
.card,
.app {
min-width: 0;
padding: clamp(14px, 1.8vw, 22px);
border: 1px solid var(--shd-line);
border-radius: var(--shd-radius-md);
background: var(--shd-surface);
box-shadow: var(--shd-shadow);
}
.card > * + *,
.app > * + *,
.shell > * + *,
.visual > * + * {
margin-block-start: var(--shd-space-4);
}
.card > .eyebrow + h1,
.card > h1 + .lede,
.app > .header + .toolbar {
margin-block-start: var(--shd-space-1);
}
.card > .summary:empty,
.card > .status:empty,
.app > .summary:empty,
.app > .status:empty {
display: none;
}
.eyebrow {
color: var(--shd-muted);
font-size: 11px;
font-weight: 750;
letter-spacing: .12em;
text-transform: uppercase;
}
h1,
h2,
h3,
p {
overflow-wrap: anywhere;
}
h1 {
margin: 0;
font-size: clamp(22px, 2.5vw, 30px);
line-height: 1.15;
letter-spacing: -.02em;
}
.lede,
.subtitle {
margin: 0;
color: var(--shd-muted);
}
.header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--shd-space-5);
}
.toolbar {
display: flex;
align-items: center;
gap: var(--shd-space-2);
flex-wrap: wrap;
}
.toolbar > .search {
flex: 1 1 220px;
}
.search,
.toolbar input,
.toolbar select,
.setting input,
.setting select,
.shd-widget-actions-select,
.shd-widget-actions-input {
min-width: 0;
border: 1px solid var(--shd-line-strong);
border-radius: var(--shd-radius-sm);
background: var(--shd-bg);
color: var(--shd-ink);
outline: 0;
}
.search,
.toolbar input,
.toolbar select {
padding: 10px 12px;
}
.toolbar select {
min-width: 150px;
}
.search:focus,
.toolbar input:focus,
.toolbar select:focus,
.setting input:focus,
.setting select:focus,
.shd-widget-actions-select:focus,
.shd-widget-actions-input:focus {
border-color: var(--shd-accent);
box-shadow: 0 0 0 3px var(--shd-accent-soft);
}
button {
cursor: pointer;
}
button:focus-visible,
[role="button"]:focus-visible {
outline: 3px solid var(--shd-accent-soft);
outline-offset: 2px;
}
.button,
.refresh,
.toolbar button,
.actions button,
.inline-action {
border: 1px solid var(--shd-line-strong);
border-radius: var(--shd-radius-sm);
padding: 9px 13px;
background: var(--shd-surface-raised);
color: var(--shd-ink);
font-weight: 650;
white-space: nowrap;
}
.button:hover,
.toolbar button:hover,
.actions button:hover,
.inline-action:hover {
border-color: var(--shd-accent);
background: var(--shd-surface-subtle);
}
.refresh,
.toolbar .primary,
.actions button,
.inline-action,
.app #refresh {
border-color: var(--shd-accent);
background: var(--shd-accent);
color: var(--shd-on-accent);
}
.refresh:hover,
.toolbar .primary:hover,
.actions button:hover,
.inline-action:hover,
.app #refresh:hover {
border-color: var(--shd-accent-strong);
background: var(--shd-accent-strong);
}
.refresh:disabled,
.toolbar button:disabled,
.actions button:disabled,
.button:disabled,
.inline-action:disabled {
cursor: wait;
opacity: .58;
}
.status {
min-height: 0;
color: var(--shd-muted);
}
.status.error,
.error {
color: var(--shd-danger);
}
.summary {
display: flex;
flex-wrap: wrap;
gap: var(--shd-space-2);
}
.summary span,
.summary-item,
.pill {
display: inline-flex;
align-items: center;
max-width: 100%;
min-height: 26px;
padding: 4px 9px;
overflow: hidden;
border: 1px solid transparent;
border-radius: 999px;
background: var(--shd-surface-subtle);
color: var(--shd-muted);
font-size: 12px;
line-height: 1.25;
text-overflow: ellipsis;
white-space: nowrap;
}
.summary strong,
.summary-item strong {
margin-inline-end: 4px;
color: var(--shd-ink);
font-weight: 700;
}
.pill.good,
.pill.up,
.pill.ok,
.pill.active,
.status-done {
border-color: var(--shd-line);
background: var(--shd-good-soft);
color: var(--shd-good);
}
.pill.bad,
.pill.down,
.pill.error,
.pill.overdue,
.status-danger {
border-color: var(--shd-line);
background: var(--shd-danger-soft);
color: var(--shd-danger);
}
.status-progress {
background: var(--shd-accent-soft);
color: var(--shd-accent-strong);
}
.status-neutral {
background: var(--shd-surface-subtle);
color: var(--shd-muted);
}
.status-warning {
background: var(--shd-warning-soft);
color: var(--shd-warning);
}
.table-wrap,
.table,
.matrix-wrap {
min-width: 0;
overflow: auto;
border: 1px solid var(--shd-line-strong);
border-radius: var(--shd-radius-md);
background: var(--shd-surface-raised);
box-shadow: var(--shd-shadow-soft);
}
.visual {
min-width: 0;
overflow: auto;
}
.table-wrap {
max-height: 520px;
}
.visual {
padding: 0;
}
table {
width: 100%;
min-width: 560px;
border-collapse: separate;
border-spacing: 0;
}
th,
td {
padding: 13px 15px;
border-bottom: 1px solid var(--shd-line);
text-align: left;
vertical-align: top;
}
th {
position: sticky;
top: 0;
z-index: 1;
background: var(--shd-surface-subtle);
color: var(--shd-muted);
font-size: 12px;
font-weight: 700;
letter-spacing: .01em;
}
tbody tr:last-child td,
tr:last-child td {
border-bottom: 0;
}
tbody tr:hover,
.record:hover,
.record:focus,
.project-row:hover {
background: var(--shd-accent-soft);
}
.record {
cursor: pointer;
outline: 0;
}
.title,
.project-title,
.visual-title {
font-weight: 700;
}
.sub,
.project-code,
.visual-date,
.visual-value,
.muted,
.loading {
color: var(--shd-muted);
}
.sub,
.project-code {
display: block;
margin-top: var(--shd-space-1);
font-size: 12px;
}
.empty {
padding: var(--shd-space-6) var(--shd-space-4);
color: var(--shd-muted);
text-align: center;
}
.visual[hidden],
.details[hidden],
.error[hidden] {
display: none;
}
.visual-card {
display: block;
width: 100%;
min-width: 0;
padding: 14px 15px;
border: 1px solid var(--shd-line);
border-radius: var(--shd-radius-sm);
background: var(--shd-surface-raised);
color: inherit;
text-align: left;
cursor: pointer;
}
.visual-card:hover,
.visual-card:focus {
border-color: var(--shd-accent);
background: var(--shd-surface-subtle);
outline: 0;
}
.visual-title {
display: block;
overflow-wrap: anywhere;
}
.visual-date,
.visual-value {
display: block;
margin-top: var(--shd-space-1);
font-size: 12px;
}
.visual-line {
display: flex;
align-items: center;
gap: var(--shd-space-2);
flex-wrap: wrap;
margin-top: var(--shd-space-2);
}
.grid,
.metric-grid,
.preview-grid,
.calendar,
.kanban {
display: grid;
min-width: 0;
gap: var(--shd-space-4);
}
#content {
display: grid;
min-width: 0;
gap: var(--shd-space-5);
}
.grid,
.preview-grid {
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
.metric-grid {
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.panel,
.metric,
.progress-card,
.preview-card,
.kanban-col,
.calendar-day,
.detail {
min-width: 0;
border: 1px solid var(--shd-line);
border-radius: var(--shd-radius-md);
background: var(--shd-surface-raised);
box-shadow: var(--shd-shadow-soft);
}
.panel {
padding: var(--shd-space-5);
}
.panel h2,
.panel h3 {
margin: 0 0 var(--shd-space-3);
color: var(--shd-ink);
font-size: 16px;
line-height: 1.25;
}
.panel p {
margin: 0;
color: var(--shd-muted);
}
.metric {
padding: var(--shd-space-4);
}
.metric-label {
display: block;
color: var(--shd-muted);
font-size: 12px;
font-weight: 600;
}
.metric-value {
display: block;
margin-top: var(--shd-space-2);
color: var(--shd-ink);
font-size: clamp(22px, 3vw, 30px);
font-weight: 750;
letter-spacing: -.02em;
overflow-wrap: anywhere;
}
.metric p {
margin: var(--shd-space-2) 0 0;
color: var(--shd-ink);
font-size: clamp(20px, 2.7vw, 28px);
font-weight: 750;
letter-spacing: -.02em;
}
.kanban {
grid-auto-flow: column;
grid-auto-columns: minmax(210px, 1fr);
align-items: start;
overflow-x: auto;
}
.kanban-col {
min-width: 210px;
padding: var(--shd-space-3);
}
.kanban-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--shd-space-2);
margin-bottom: var(--shd-space-3);
font-weight: 700;
}
.kanban-list,
.source-list,
.tree {
display: grid;
gap: var(--shd-space-2);
}
.metric-grid + .kanban-list {
margin-top: var(--shd-space-1);
}
.timeline {
display: grid;
gap: 0;
}
.timeline-item {
display: grid;
position: relative;
grid-template-columns: 18px minmax(0, 1fr);
gap: var(--shd-space-3);
padding-bottom: var(--shd-space-4);
}
.timeline-rail {
position: absolute;
top: 16px;
bottom: 0;
left: 8px;
width: 2px;
background: var(--shd-line);
}
.timeline-item:last-child .timeline-rail {
display: none;
}
.timeline-dot {
z-index: 1;
width: 16px;
height: 16px;
border: 3px solid var(--shd-accent-soft);
border-radius: 50%;
background: var(--shd-accent);
}
.timeline-content {
min-width: 0;
}
.timeline-meta {
margin-bottom: var(--shd-space-2);
color: var(--shd-muted);
font-size: 12px;
}
.funnel {
display: grid;
gap: var(--shd-space-3);
}
.funnel-row {
display: grid;
grid-template-columns: minmax(100px, 1fr) minmax(120px, 2fr) auto;
align-items: center;
gap: var(--shd-space-3);
}
progress {
width: 100%;
height: 10px;
overflow: hidden;
border: 0;
border-radius: 999px;
background: var(--shd-surface-subtle);
accent-color: var(--shd-accent);
}
progress::-webkit-progress-bar {
border-radius: 999px;
background: var(--shd-surface-subtle);
}
progress::-webkit-progress-value {
border-radius: 999px;
background: var(--shd-accent);
}
progress::-moz-progress-bar {
border-radius: 999px;
background: var(--shd-accent);
}
.progress-card {
display: grid;
gap: var(--shd-space-2);
padding: var(--shd-space-4);
}
.preview-card {
padding: var(--shd-space-4);
}
.preview-fields {
display: grid;
gap: var(--shd-space-2);
margin-top: var(--shd-space-3);
}
.preview-field {
overflow-wrap: anywhere;
font-size: 12px;
}
.preview-field strong {
color: var(--shd-muted);
font-weight: 600;
}
.calendar {
grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.calendar-day {
display: grid;
align-content: start;
gap: var(--shd-space-3);
padding: var(--shd-space-3);
}
.calendar-head {
font-weight: 700;
}
.matrix-wrap {
background: var(--shd-surface-raised);
}
.matrix-table {
min-width: 600px;
}
.matrix-table th,
.matrix-table td {
padding: 12px 14px;
}
.tree-children {
display: grid;
gap: var(--shd-space-2);
margin-left: var(--shd-space-3);
padding-left: var(--shd-space-4);
border-left: 1px solid var(--shd-line);
}
.details {
min-width: 0;
padding: var(--shd-space-5);
border: 1px solid var(--shd-line-strong);
border-radius: var(--shd-radius-md);
background: var(--shd-surface-subtle);
box-shadow: var(--shd-shadow-soft);
}
.detail-head,
.details-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--shd-space-4);
}
.details h2,
.details-title {
margin: 0;
font-size: 18px;
line-height: 1.25;
}
.details-grid,
.grid {
margin-top: var(--shd-space-4);
}
.detail {
padding: var(--shd-space-3);
}
.label,
.detail-label {
display: block;
margin-bottom: var(--shd-space-1);
color: var(--shd-muted);
font-size: 11px;
}
.value,
.detail-value {
display: block;
overflow-wrap: anywhere;
white-space: pre-wrap;
}
.close {
border: 0;
background: transparent;
color: var(--shd-muted);
font-size: 20px;
}
.chart {
min-height: 176px;
margin: var(--shd-space-3) 0 0;
padding: var(--shd-space-2);
overflow: hidden;
border: 1px solid var(--shd-line);
border-radius: var(--shd-radius-sm);
background: var(--shd-surface-subtle);
}
.chart svg {
display: block;
width: 100%;
height: 160px;
}
.chart-empty {
display: grid;
height: 156px;
place-items: center;
color: var(--shd-muted);
font-size: 12px;
}
.axis {
fill: var(--shd-muted);
font-size: 10px;
}
.chart-grid-line {
stroke: var(--shd-line);
stroke-dasharray: 3 4;
stroke-width: 1;
}
.chart-axis-line {
stroke: var(--shd-line-strong);
stroke-width: 1;
}
.chart-x-label,
.chart-y-label {
fill: var(--shd-muted);
font-size: 10px;
font-variant-numeric: tabular-nums;
}
.chart-line {
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2.4;
vector-effect: non-scaling-stroke;
}
.chart-area {
stroke: none;
opacity: .12;
pointer-events: none;
}
.line-a,
.line-b {
fill: none;
}
.line-a {
stroke: #5e91c0;
}
.line-b {
stroke: #d48655;
}
.line-c {
stroke: #73b987;
}
.line-d {
stroke: #b58bd9;
}
.line-e {
stroke: #d9ad5c;
}
.line-f {
stroke: #63b9c9;
}
.chart-area.line-a {
fill: #5e91c0;
}
.chart-area.line-b {
fill: #d48655;
}
.chart-area.line-c {
fill: #73b987;
}
.chart-area.line-d {
fill: #b58bd9;
}
.chart-area.line-e {
fill: #d9ad5c;
}
.chart-area.line-f {
fill: #63b9c9;
}
.comparison-chart {
min-height: 0;
padding: 8px 10px 6px;
}
.comparison-view {
display: grid;
min-width: 0;
gap: var(--shd-space-3);
}
.comparison-chart svg {
height: clamp(260px, 31vw, 360px);
}
.chart-legend {
display: grid;
gap: 0;
margin-top: var(--shd-space-3);
}
.chart-legend-row {
display: grid;
grid-template-columns: auto minmax(180px, 1fr) repeat(4, minmax(66px, auto));
align-items: center;
gap: var(--shd-space-2) var(--shd-space-3);
min-width: 0;
padding: 9px 2px;
border-top: 1px solid var(--shd-line);
}
.chart-legend-row:first-child {
padding-top: 0;
border-top: 0;
}
.chart-swatch {
width: 18px;
height: 3px;
border-radius: 999px;
}
.chart-swatch.line-a {
background: #5e91c0;
}
.chart-swatch.line-b {
background: #d48655;
}
.chart-swatch.line-c {
background: #73b987;
}
.chart-swatch.line-d {
background: #b58bd9;
}
.chart-swatch.line-e {
background: #d9ad5c;
}
.chart-swatch.line-f {
background: #63b9c9;
}
.chart-legend-name,
.chart-legend-stat {
min-width: 0;
overflow-wrap: anywhere;
}
.chart-legend-name {
display: grid;
gap: 2px;
color: var(--shd-ink);
font-weight: 700;
}
.chart-legend-meta {
display: block;
color: var(--shd-muted);
font-size: 10px;
font-weight: 400;
}
.chart-legend-error {
color: var(--shd-danger);
}
.chart-legend-stat {
display: grid;
gap: 1px;
color: var(--shd-ink);
font-variant-numeric: tabular-nums;
text-align: right;
white-space: nowrap;
}
.chart-legend-stat-label {
margin-right: 0;
color: var(--shd-muted);
font-size: 11px;
}
.source-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--shd-space-3);
min-width: 0;
padding: 10px 12px;
border: 1px solid var(--shd-line);
border-radius: var(--shd-radius-sm);
background: var(--shd-surface-subtle);
}
.source-row > * {
min-width: 0;
overflow-wrap: anywhere;
}
.setting {
display: grid;
grid-template-columns: minmax(130px, 1fr) minmax(150px, 1fr);
align-items: center;
gap: var(--shd-space-3);
padding: var(--shd-space-3) 0;
border-bottom: 1px solid var(--shd-line);
}
.setting:last-child {
border-bottom: 0;
}
.setting label {
font-weight: 700;
}
.setting small {
display: block;
color: var(--shd-muted);
font-weight: 400;
}
.setting input,
.setting select {
width: 100%;
padding: 9px 10px;
}
.setting input:disabled,
.setting select:disabled {
opacity: .6;
}
.actions {
display: flex;
justify-content: flex-end;
gap: var(--shd-space-2);
margin-top: var(--shd-space-4);
}
.inline-action {
padding: 6px 9px;
font-size: 12px;
}
/* Active-projects keeps its own data renderer but shares this visual contract. */
.app {
max-width: 1040px;
}
.app .header h1 {
font-size: clamp(21px, 2.4vw, 28px);
}
.app .subtitle {
margin-top: var(--shd-space-1);
font-size: 13px;
}
.app .table {
overflow: hidden;
}
.app .table-head,
.app .project-row {
display: grid;
grid-template-columns: minmax(0, 1.7fr) minmax(130px, .8fr) minmax(110px, .7fr);
align-items: center;
gap: var(--shd-space-3);
}
.app .table-head {
padding: 11px 16px;
background: var(--shd-surface-subtle);
color: var(--shd-muted);
font-size: 11px;
font-weight: 700;
letter-spacing: .04em;
text-transform: uppercase;
}
.app .project-row {
width: 100%;
padding: 14px 16px;
border: 0;
border-top: 1px solid var(--shd-line);
background: var(--shd-surface-raised);
text-align: left;
}
.app .project-name,
.app .project-title {
min-width: 0;
}
.app .project-title {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.app .deadline {
color: var(--shd-ink);
font-size: 13px;
}
.app .deadline.missing {
color: var(--shd-muted);
}
.app .deadline.overdue {
color: var(--shd-danger);
font-weight: 700;
}
.app .status {
display: inline-flex;
width: fit-content;
max-width: 100%;
padding: 5px 9px;
overflow: hidden;
border-radius: 999px;
font-size: 12px;
font-weight: 650;
text-overflow: ellipsis;
white-space: nowrap;
}
.app .details {
background: var(--shd-surface-subtle);
}
@media (max-width: 720px) {
.shell,
.app {
padding: 8px;
}
.card,
.app {
padding: 14px;
border-radius: var(--shd-radius-md);
}
.header {
display: block;
}
.header .button,
.header #refresh {
margin-top: var(--shd-space-3);
}
.toolbar > * {
flex: 1 1 180px;
}
.toolbar .refresh,
.toolbar button,
.toolbar select {
flex-grow: 0;
}
.grid,
.preview-grid {
grid-template-columns: 1fr;
}
.metric-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.chart-legend-row {
grid-template-columns: auto minmax(0, 1fr) repeat(2, minmax(72px, auto));
}
.chart-legend-stat {
grid-column: auto;
}
.chart-legend-stat:nth-of-type(3) {
grid-column: 3;
}
.chart-legend-stat:nth-of-type(4) {
grid-column: 4;
}
.chart-legend-stat:nth-of-type(5) {
grid-row: 2;
grid-column: 3;
}
.chart-legend-stat:nth-of-type(6) {
grid-row: 2;
grid-column: 4;
}
.setting {
grid-template-columns: 1fr;
}
.funnel-row {
grid-template-columns: 1fr auto;
}
.funnel-row progress {
grid-column: 1 / -1;
grid-row: 2;
}
.app .table-head,
.app .project-row {
grid-template-columns: minmax(0, 1.4fr) minmax(100px, .8fr) minmax(90px, .7fr);
gap: var(--shd-space-2);
padding-right: 10px;
padding-left: 10px;
}
.details-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 440px) {
.metric-grid,
.details-grid {
grid-template-columns: 1fr;
}
.detail-head,
.details-header {
display: block;
}
.close {
margin-top: var(--shd-space-2);
}
}
</style>
</head>
<body>
<main class="shell" data-shd-widget-engine="shd-universal-v1" data-widget-module="controller-observability"><section class="card">
<div class="eyebrow">SHD MCP · controller observability</div>
<h1 id="title">Контроллеры</h1><p class="lede" id="lede"></p>
<div class="toolbar"><button id="refresh" class="primary" type="button">Обновить</button><select id="source" hidden><option value="auto" data-i18n="sourceAuto">Автоматически</option><option value="native" data-i18n="sourceNative">Нативный источник</option><option value="influx" data-i18n="sourceInflux">Influx/Grafana</option></select></div>
<div id="status" class="status" role="status" aria-live="polite"></div><div id="summary" class="summary"></div><div id="content"></div>
</section></main>
<script>
(function(){'use strict';
var T={
ru:{
history:['История контроллера','Временные точки и происхождение данных.'],
compare:['Сравнение рядов','Универсальное сравнение временных рядов на общей шкале.'],
settings:['Экспертные настройки','Поля доступны для записи только при явном разрешении источника.'],
topology:['Топология контроллеров','Все контроллеры проекта; устройства source-контроллера открываются отдельным режимом.'],
devices:['Устройства контроллера','Инвентарь устройств с текущими значениями и профилем выбранной строки.'],
'live-state':['Текущие состояния','Нормализованные состояния устройств с freshness, quality и readback.'],
statistics:['Статистика состояния','Серверный расчёт событий, периодов, длительностей и почасовой разбивки.'],
health:['Здоровье телеметрии','Состояние нативного источника и Influx/Grafana.'],
audit:['Аудит операций','Команды, подтверждения и readback по source-контроллеру.'],
inventorySeparate:'Инвентарь устройств загружается отдельным запросом по выбранному контроллеру.',
refresh:'Обновить',loading:'Загрузка…',empty:'Нет данных для отображения.',records:'записей',
deviceCount:'устройств',controllerCount:'контроллеров',stateCount:'состояний',samples:'выборок',sampleOne:'выборка',sampleFew:'выборки',sampleMany:'выборок',
sourceAuto:'Автоматически',sourceNative:'Нативный источник',sourceInflux:'Influx/Grafana',
native:'Нативный источник',influx:'Influx/Grafana',available:'доступен',unavailable:'недоступен',
configured:'настроен',points:'точек',source:'Источник',actualSource:'Фактический источник',sourceDetails:'Источник и качество',statisticsSummary:'Сводка статистики',summaryValue:'Значения',notAvailable:'нет данных',
chartTitle:'Общий график',last:'последнее',seriesOne:'ряд',seriesFew:'ряда',seriesMany:'рядов',
time:'Время',value:'Значение',name:'Название',controller:'Контроллер',device:'Устройство',
room:'Комната',category:'Категория',type:'Тип',status:'Статус',error:'Ошибка',readonly:'Только чтение',
save:'Сохранить',saved:'Настройки сохранены.',noWritable:'Нет разрешённых параметров для записи.',
required:'Обязательное поле',unknown:'Неизвестный формат ответа.',fail:'Не удалось загрузить данные.',
id:'ID',action:'Действие',requested:'Запрошено',actual:'Фактически',revision:'Ревизия',checked:'Проверено',
details:'Подробнее',profile:'Профиль устройства',state:'Состояние',quality:'Качество',
freshness:'Свежесть',readback:'Readback',access:'Доступ',unit:'Единица',kind:'Тип данных',
events:'событий',transitions:'переключений',periods:'периодов',activeTime:'активное время',
activeShare:'доля активности',averageDuration:'средняя длительность',medianDuration:'медиана',
shortest:'самый короткий',longest:'самый длинный',coverage:'охват',intervals:'Периоды активности',
hourly:'По часам',bucket:'Час',duration:'Длительность',start:'Начало',end:'Конец',
second:'с',minute:'мин',hour:'ч',day:'д',binary:'бинарный',numeric:'числовой',
minimum:'минимум',maximum:'максимум',average:'среднее',open:'открытый',
yes:'да',no:'нет',truncated:'выборка ограничена',noStats:'Статистика недоступна для выбранного диапазона или источника.',
noItems:'Устройства не найдены.',unavailableReason:'Причина недоступности'
},
en:{
history:['Controller history','Time-series points and data provenance.'],
compare:['Series comparison','Universal time-series comparison on one shared scale.'],
settings:['Expert settings','Fields are writable only when the source explicitly allows it.'],
topology:['Controller topology','All project controllers; source-controller devices open in a separate mode.'],
devices:['Controller devices','Device inventory with current values and a profile action for the selected row.'],
'live-state':['Current live state','Normalized device states with freshness, quality and readback.'],
statistics:['State statistics','Server-computed events, periods, durations and hourly buckets.'],
health:['Telemetry health','Native-source and Influx/Grafana status.'],
audit:['Operation audit','Commands, confirmations and readback for the source controller.'],
inventorySeparate:'Device inventory is loaded by a separate request for the selected controller.',
refresh:'Refresh',loading:'Loading…',empty:'No data to display.',records:'records',
deviceCount:'devices',controllerCount:'controllers',stateCount:'states',samples:'samples',sampleOne:'sample',sampleFew:'samples',sampleMany:'samples',
sourceAuto:'Automatic',sourceNative:'Native source',sourceInflux:'Influx/Grafana',
native:'Native source',influx:'Influx/Grafana',available:'available',unavailable:'unavailable',
configured:'configured',points:'points',source:'Source',actualSource:'Actual source',sourceDetails:'Source and quality',statisticsSummary:'Statistics summary',summaryValue:'Values',notAvailable:'no data',
chartTitle:'Shared chart',last:'last',seriesOne:'series',seriesFew:'series',seriesMany:'series',
time:'Time',value:'Value',name:'Name',controller:'Controller',device:'Device',
room:'Room',category:'Category',type:'Type',status:'Status',error:'Error',readonly:'Read-only',
save:'Save',saved:'Settings saved.',noWritable:'No writable parameters are allowed.',
required:'Required field',unknown:'Unknown response format.',fail:'Could not load data.',
id:'ID',action:'Action',requested:'Requested',actual:'Actual',revision:'Revision',checked:'Checked',
details:'Details',profile:'Device profile',state:'State',quality:'Quality',
freshness:'Freshness',readback:'Readback',access:'Access',unit:'Unit',kind:'Data type',
events:'events',transitions:'transitions',periods:'periods',activeTime:'active time',
activeShare:'active share',averageDuration:'average duration',medianDuration:'median',
shortest:'shortest',longest:'longest',coverage:'coverage',intervals:'Activity periods',
hourly:'Hourly',bucket:'Hour',duration:'Duration',start:'Start',end:'End',
second:'s',minute:'min',hour:'h',day:'d',binary:'binary',numeric:'numeric',
minimum:'minimum',maximum:'maximum',average:'average',open:'open',
yes:'yes',no:'no',truncated:'sample limited',noStats:'Statistics are unavailable for this range or source.',
noItems:'No devices found.',unavailableReason:'Unavailable reason'
}
};
function localTimezone(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone||''}catch(err){return''}}
var state={mode:'history',config:{},data:null,busy:false,timezone:localTimezone(),lang:(String((window.openai&&window.openai.locale)||document.documentElement.lang||'ru').toLowerCase().indexOf('en')===0?'en':'ru')};
var pending=new Map(),nextId=1;
var el={title:document.getElementById('title'),lede:document.getElementById('lede'),refresh:document.getElementById('refresh'),source:document.getElementById('source'),status:document.getElementById('status'),summary:document.getElementById('summary'),content:document.getElementById('content')};
function t(k){return(T[state.lang]&&T[state.lang][k])||k}
function obj(x){return x&&typeof x==='object'&&!Array.isArray(x)}
function text(x){if(x===null||x===undefined||x==='')return'—';if(typeof x==='object')try{return JSON.stringify(x)}catch(e){return'—'}return String(x)}
function esc(x){return text(x).replace(/[&<>"']/g,function(c){return({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'})[c]})}
function unwrap(x){var p=x&&x.structuredContent?x.structuredContent:(x&&x.toolOutput?x.toolOutput:x);if(p&&p.isError)throw Error(t('fail'));return p||{}}
function callData(x){var p=unwrap(x);return p&&obj(p.data)&&!p.schema?p.data:p}
function merge(a,b){return Object.assign({},a||{},b||{})}
function setStatus(v,bad){el.status.textContent=v||'';el.status.className=bad?'status error':'status'}
function pill(v,kind){return '<span class="pill '+(kind||'')+'">'+esc(v)+'</span>'}
function clear(){el.content.replaceChildren();el.summary.replaceChildren()}
function rpc(method,params){var o=window.openai;if(method==='tools/call'&&o&&typeof o.callTool==='function')return Promise.resolve(o.callTool(params.name,params.arguments||{}));var id='controller-'+nextId++;window.parent.postMessage({jsonrpc:'2.0',id:id,method:method,params:params||{}},'*');return new Promise(function(resolve,reject){var timeout=setTimeout(function(){pending.delete(id);reject(Error(t('fail')))},15000);pending.set(id,{resolve:resolve,reject:reject,timeout:timeout})})}
function metaFrom(p){var m=obj(p.meta)?p.meta:{},w=obj(m.widget)?m.widget:{};state.mode=String(w.mode||m.mode||state.mode||'history');state.config=merge(state.config,w);if(obj(w.sourceArgs)&&Object.keys(w.sourceArgs).length)state.config.sourceArgs=w.sourceArgs;else if(obj(m.sourceArgs)&&Object.keys(m.sourceArgs).length)state.config.sourceArgs=m.sourceArgs;return m}
function dataFrom(p){var source=unwrap(p);metaFrom(source);var data=source.meta&&source.meta.widget&&source.data!==undefined?source.data:source,timezone=data&&data.timezone,timezoneSource=data&&data.timezone_source;if(!timezone&&data&&Array.isArray(data.series)){var candidate=data.series.find(function(series){return series&&series.timezone});timezone=candidate&&candidate.timezone;timezoneSource=candidate&&candidate.timezone_source}if(typeof timezone==='string'&&timezone&&timezoneSource!=='normalized_utc'&&timezoneSource!=='influx_timestamp')state.timezone=timezone;return data}
function translateOptions(select){select.querySelectorAll('[data-i18n]').forEach(function(option){option.textContent=t(option.getAttribute('data-i18n'))})}
function labels(){var heading=T[state.lang][state.mode]||T[state.lang].history,isCollection=state.mode==='statistics'&&state.data&&Array.isArray(state.data.series);document.documentElement.lang=state.lang;el.title.textContent=heading[0];el.lede.textContent=heading[1];el.refresh.textContent=t('refresh');translateOptions(el.source);el.source.setAttribute('aria-label',t('source'));el.refresh.hidden=!!isCollection;el.source.hidden=isCollection||!['history','compare','health','statistics'].includes(state.mode);if(state.config.sourceArgs&&state.config.sourceArgs.source){var requested=String(state.config.sourceArgs.source).toLowerCase();el.source.value=['auto','native','influx'].includes(requested)?requested:'auto'}}
function summary(items){el.summary.replaceChildren();(items||[]).forEach(function(x){var s=document.createElement('span');s.className='pill '+(x.kind||'');s.textContent=text(x.value);el.summary.append(s)})}
function formatNumber(value){var number=Number(value);if(value===null||value===undefined||value===''||!Number.isFinite(number))return text(value);return number.toLocaleString(state.lang==='ru'?'ru-RU':'en-US',{maximumFractionDigits:2})}
function dateOnlyText(raw){var parts=raw.split('-').map(Number);if(parts.length!==3||parts.some(function(part){return!Number.isFinite(part)}))return null;var date=new Date(Date.UTC(parts[0],parts[1]-1,parts[2],12));try{return new Intl.DateTimeFormat(state.lang,{dateStyle:'medium',timeZone:'UTC'}).format(date)}catch(err){return null}}
function dateText(value){if(value===null||value===undefined||value==='')return'—';var timestamp;if(value instanceof Date)timestamp=value.getTime();else if(typeof value==='number')timestamp=Math.abs(value)<100000000000?value*1000:value;else{var raw=String(value).trim();if(/^\d{4}-\d{2}-\d{2}$/.test(raw)){var only=dateOnlyText(raw);if(only)return only}var numeric=Number(raw);timestamp=raw!==''&&Number.isFinite(numeric)?(Math.abs(numeric)<100000000000?numeric*1000:numeric):Date.parse(raw);if(Number.isNaN(timestamp)&&raw)timestamp=Date.parse(raw.replace(/([+-]\d{2}):(\d{2})$/,'$1$2'))}if(!Number.isFinite(timestamp))return String(value);var options={dateStyle:'medium',timeStyle:'medium'};if(state.timezone)options.timeZone=state.timezone;try{return new Intl.DateTimeFormat(state.lang,options).format(new Date(timestamp))}catch(err){try{return new Intl.DateTimeFormat(state.lang,{dateStyle:'medium',timeStyle:'medium'}).format(new Date(timestamp))}catch(fallback){return String(value)}}}
function sampleLabel(value){var number=Number(value);if(!Number.isFinite(number))number=0;var integer=Math.abs(Math.trunc(number)),word;if(state.lang==='ru'){var lastTwo=integer%100,last=integer%10;word=last===1&&lastTwo!==11?t('sampleOne'):last>=2&&last<=4&&(lastTwo<10||lastTwo>=20)?t('sampleFew'):t('sampleMany')}else{word=integer===1?t('sampleOne'):t('sampleMany')}return formatNumber(number)+' '+word}
function seriesLabel(value){var number=Number(value);if(!Number.isFinite(number))number=0;var integer=Math.abs(Math.trunc(number)),word;if(state.lang==='ru'){var lastTwo=integer%100,last=integer%10;word=last===1&&lastTwo!==11?t('seriesOne'):last>=2&&last<=4&&(lastTwo<10||lastTwo>=20)?t('seriesFew'):t('seriesMany')}else word=t('seriesMany');return formatNumber(number)+' '+word}
function formatDuration(value){var seconds=Number(value);if(!Number.isFinite(seconds))return text(value);seconds=Math.max(0,Math.round(seconds));var parts=[];if(seconds>=86400){parts.push(Math.floor(seconds/86400)+' '+t('day'));seconds%=86400}if(seconds>=3600){parts.push(Math.floor(seconds/3600)+' '+t('hour'));seconds%=3600}if(seconds>=60){parts.push(Math.floor(seconds/60)+' '+t('minute'));seconds%=60}if(seconds||parts.length===0)parts.push(seconds+' '+t('second'));return parts.join(' ')}
function formatFreshness(value){var ms=Number(value);return Number.isFinite(ms)?formatDuration(ms/1000):text(value)}
function sourceLabel(value){return value==='native'?t('native'):value==='influx'?t('influx'):text(value)}
function sourceArgs(){return obj(state.config.sourceArgs)?state.config.sourceArgs:{}}
function statisticsSeriesScope(d,useArgs){var args=useArgs?sourceArgs():{},room=String(d.room||d.room_name||d.room_label||args.room||'').trim(),item=String(d.item_name||d.label||d.name||d.item_id||args.item_id||'').trim(),controller=String(d.controller_name||d.controller||d.source_controller_id||args.controller||'').trim(),project=String(d.project||args.project||'').trim();return{project:project,controller:controller,room:room,item:item}}
function scopeLabel(d){var scope=statisticsSeriesScope(d,true),parts=[];[scope.project,scope.controller,scope.room,scope.item].forEach(function(value){if(value&&!parts.includes(value))parts.push(value)});return parts.join(' / ')}
function statisticsSourcePill(d){var args=sourceArgs(),requested=String(d.history_source_requested||args.source||'auto').trim().toLowerCase()||'auto',selected=String(d.selected_history_source||'').trim().toLowerCase();if(requested==='auto'||requested==='all'||(selected!==''&&selected!==requested))return{value:t('actualSource')+': '+sourceLabel(selected||requested),kind:selected?'good':'bad'};return null}
function statisticsValueText(d){var s=obj(d.summary)?d.summary:{},kind=d.statistics_kind,parts=[];if(kind==='numeric'){parts.push(t('samples')+': '+formatNumber(s.sample_count),t('minimum')+': '+formatNumber(s.min),t('maximum')+': '+formatNumber(s.max),t('average')+': '+formatNumber(s.average),t('median')+': '+formatNumber(s.median))}else if(kind==='binary'){parts.push(t('events')+': '+formatNumber(s.event_count),t('transitions')+': '+formatNumber(s.transition_count),t('periods')+': '+formatNumber(s.period_count),t('activeShare')+': '+formatNumber(s.active_percent)+'%')}else{parts.push(t('notAvailable'))}return parts.join('; ')}
function renderStatisticsCollection(d){var list=Array.isArray(d.series)?d.series:[],first=list.find(function(item){return obj(item)})||{},from=d.from||first.from,to=d.to||first.to,available=list.filter(function(item){return item&&item.statistics_available===true}).length;summary([{value:seriesLabel(list.length)},{value:available+' '+t('available')},{value:dateText(from)+' → '+dateText(to)}]);if(!list.length){el.content.append(empty());return}var rows=list.map(function(item){var scope=statisticsSeriesScope(item,false),selected=item.selected_history_source||item.history_source_requested||item.source,hasStats=item.statistics_available===true|| (item.statistics_available===undefined&&item.available!==false),status=hasStats?t('available'):text(item.statistics_error_code||item.error_code||t('notAvailable'));return[text(scope.room),text(scope.item),text(scope.controller),sourceLabel(selected),kindLabel(item.statistics_kind),text(item.quality),status,statisticsValueText(item)]});el.content.append(card(t('statisticsSummary'),table([t('room'),t('device'),t('controller'),t('source'),t('kind'),t('quality'),t('status'),t('summaryValue')],rows)))}
function kindLabel(value){return value==='binary'?t('binary'):value==='numeric'?t('numeric'):text(value)}
function boolLabel(value){return value?t('yes'):t('no')}
function numberRange(points,binary){
var min=null,max=null;
(points||[]).forEach(function(point){
var value=Number(point&&point.value);
if(!Number.isFinite(value))return;
if(min===null||value<min)min=value;
if(max===null||value>max)max=value;
});
if(min===null)return{min:0,max:1,binary:!!binary};
if(binary){min=0;max=1}
else if(min===max){var padding=Math.abs(min)||1;min-=padding;max+=padding}
else{var range=max-min;min-=range*.08;max+=range*.08}
return{min:min,max:max,binary:!!binary};
}
function chartTimeLabel(value){
if(!Number.isFinite(value))return'';
var options={day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'};
if(state.timezone)options.timeZone=state.timezone;
try{return new Intl.DateTimeFormat(state.lang,options).format(new Date(value))}catch(err){return''}
}
function chart(points,cls){
var source=Array.isArray(points)?points:[],data=source.map(function(point,index){return{value:Number(point&&point.value),time:pointTimestamp(point),order:index}}).filter(function(point){return Number.isFinite(point.value)});
if(!data.length)return'<div class="chart-empty">'+esc(t('empty'))+'</div>';
var timed=data.every(function(point){return Number.isFinite(point.time)});
if(timed)data.sort(function(a,b){return a.time-b.time||a.order-b.order});
data.forEach(function(point,index){point.order=index});
var binary=data.every(function(point){return point.value===0||point.value===1});
if(data.length>120)data=sampleChartPoints(data,120);
var width=760,height=174,left=48,right=14,top=14,bottom=34,plotWidth=width-left-right,plotHeight=height-top-bottom,range=numberRange(data,binary);
function xAt(point,index){if(timed&&data.length>1&&data[data.length-1].time!==data[0].time&&Number.isFinite(point.time))return left+plotWidth*(point.time-data[0].time)/(data[data.length-1].time-data[0].time);return left+plotWidth*(data.length===1?.5:index/(data.length-1))}
function yAt(value){return top+plotHeight*(range.max-value)/(range.max-range.min)}
var coords=data.map(function(point,index){return{x:xAt(point,index),y:yAt(point.value)}});
var path='M '+coords[0].x.toFixed(1)+' '+coords[0].y.toFixed(1);
for(var i=1;i<coords.length;i++)path+=(binary?' H '+coords[i].x.toFixed(1)+' V '+coords[i].y.toFixed(1):' L '+coords[i].x.toFixed(1)+' '+coords[i].y.toFixed(1));
var ticks=range.binary?[0,.5,1]:[range.min,(range.min+range.max)/2,range.max],svg=['<svg class="shd-chart" viewBox="0 0 '+width+' '+height+'" role="img" aria-label="'+esc(t('value'))+'">','<g>'];
ticks.forEach(function(value){var y=yAt(value);svg.push('<line class="chart-grid-line" x1="'+left+'" x2="'+(width-right)+'" y1="'+y.toFixed(1)+'" y2="'+y.toFixed(1)+'"/><text class="chart-y-label" x="4" y="'+(y+3).toFixed(1)+'">'+esc(formatNumber(value))+'</text>')});
svg.push('</g><line class="chart-axis-line" x1="'+left+'" x2="'+(width-right)+'" y1="'+(height-bottom)+'" y2="'+(height-bottom)+'"/><path class="chart-line '+(cls||'line-a')+'" d="'+path+'"/>');
if(coords.length===1)svg.push('<circle class="chart-point '+(cls||'line-a')+'" cx="'+coords[0].x.toFixed(1)+'" cy="'+coords[0].y.toFixed(1)+'" r="4"><title>'+esc(formatNumber(data[0].value))+'</title></circle>');
if(timed){svg.push('<text class="chart-x-label" text-anchor="start" x="'+left+'" y="'+(height-8)+'">'+esc(chartTimeLabel(data[0].time))+'</text>');if(data[data.length-1].time!==data[0].time)svg.push('<text class="chart-x-label" text-anchor="end" x="'+(width-right)+'" y="'+(height-8)+'">'+esc(chartTimeLabel(data[data.length-1].time))+'</text>')}
return svg.join('')+'</svg>'
}
function pointTimestamp(point){
var raw=point&&(point.time!==undefined?point.time:(point.source_timestamp!==undefined?point.source_timestamp:point.timestamp));
if(raw===null||raw===undefined||raw==='')return null;
if(typeof raw==='number'){if(!Number.isFinite(raw))return null;return Math.abs(raw)<100000000000?raw*1000:raw}
var numeric=Number(raw);
if(String(raw).trim()!==''&&Number.isFinite(numeric))return Math.abs(numeric)<100000000000?numeric*1000:numeric;
var parsed=Date.parse(String(raw));
return Number.isFinite(parsed)?parsed:null
}
function comparisonPoints(series){
var source=Array.isArray(series&&series.points)?series.points:[],data=source.map(function(point,index){return{value:Number(point&&point.value),time:pointTimestamp(point),order:index}}).filter(function(point){return Number.isFinite(point.value)}),timed=data.length>0&&data.every(function(point){return Number.isFinite(point.time)});
if(timed)data.sort(function(a,b){return a.time-b.time||a.order-b.order});
data.forEach(function(point,index){point.order=index});
return data
}
function sampleChartPoints(data,limit){
if(data.length<=limit)return data;
var size=data.length/limit,sampled=[];
for(var i=0;i<limit;i++){
var from=Math.floor(i*size),to=Math.max(from+1,Math.floor((i+1)*size)),bucket=data.slice(from,to);
if(!bucket.length)continue;
var low=bucket[0],high=bucket[0];
bucket.forEach(function(point){if(point.value<low.value)low=point;if(point.value>high.value)high=point});
[bucket[0],low,high,bucket[bucket.length-1]].sort(function(a,b){return a.order-b.order}).forEach(function(point){if(!sampled.length||sampled[sampled.length-1].order!==point.order)sampled.push(point)})
}
return sampled
}
function valueUnit(item){var value=String(item&&(item.value_unit||item.unit)||'').trim(),normalized=value.toLowerCase();return ['all','auto','unknown','—'].includes(normalized)?'':value}
function seriesAxis(item){
var declared=String(item&&(item.axis||item.y_axis)||'').toLowerCase();
if(declared==='right'||declared==='secondary')return'right';
if(declared==='left'||declared==='primary')return'left';
var group=String(item&&(item.group||item.id)||'').toLowerCase(),unit=valueUnit(item).toLowerCase();
return /%|percent|процент/.test(unit)||/ventilation|вентил|heating|отопл|fan|клапан/.test(group)?'right':'left'
}
function comparisonLabel(series){
var item=series&&series.item?series.item:(series||{}),explicitLabel=item.label||item.name,label=explicitLabel||item.id||item.item_id||t('records'),meta=[];
if(item.controller)meta.push(item.controller);
var unit=valueUnit(item),source=String(item.selected_history_source||item.history_source||'').trim().toLowerCase();
if(unit)meta.push(unit);
if(source&&source!=='all')meta.push(sourceLabel(source));
if(!explicitLabel&&item.item_id&&item.id!==item.item_id)meta.unshift(item.item_id);
return{label:String(label),meta:meta.join(' · ')}
}
function comparisonChart(list){
var colors=['line-a','line-b','line-c','line-d','line-e','line-f'],items=(Array.isArray(list)?list:[]).map(function(item,index){var raw=comparisonPoints(item);return{item:item,raw:raw,points:sampleChartPoints(raw,160),color:colors[index%colors.length],axis:seriesAxis(item)}}),plotted=items.filter(function(item){return item.points.length});
if(plotted.length&&!plotted.some(function(item){return item.axis==='left'}))plotted[0].axis='left';
var all=[],axisPoints={left:[],right:[]};
plotted.forEach(function(item){all=all.concat(item.raw);axisPoints[item.axis]=axisPoints[item.axis].concat(item.raw)});
var timed=all.length>0&&all.every(function(point){return Number.isFinite(point.time)}),start=null,end=null;
all.forEach(function(point){if(!Number.isFinite(point.time))return;if(start===null||point.time<start)start=point.time;if(end===null||point.time>end)end=point.time});
function isPercentSeries(item){var unit=valueUnit(item).toLowerCase(),group=String(item&&(item.group||item.id)||'').toLowerCase();return /%|percent|процент/.test(unit)||/ventilation|вентил|fan|клапан|heating|отопл/.test(group)}
var leftBinary=axisPoints.left.length>0&&axisPoints.left.every(function(point){return point.value===0||point.value===1}),rightBinary=axisPoints.right.length>0&&axisPoints.right.every(function(point){return point.value===0||point.value===1}),rightIsPercent=axisPoints.right.length>0&&!rightBinary&&plotted.some(function(item){return item.axis==='right'&&isPercentSeries(item.item)})&&axisPoints.right.every(function(point){return point.value>=0&&point.value<=100}),ranges={left:numberRange(axisPoints.left,leftBinary),right:rightIsPercent?{min:0,max:100,binary:false}:numberRange(axisPoints.right,rightBinary)},hasRight=plotted.some(function(item){return item.axis==='right'});
var width=1200,height=360,left=70,right=70,top=20,bottom=46,plotWidth=width-left-right,plotHeight=height-top-bottom;
function xAt(point,index,points){if(timed&&start!==null&&end!==null&&end!==start&&Number.isFinite(point.time))return left+plotWidth*(point.time-start)/(end-start);return left+plotWidth*(points.length===1?.5:index/(points.length-1))}
function yAt(value,axis){var range=ranges[axis]||ranges.left;return top+plotHeight*(range.max-value)/(range.max-range.min)}
function axisUnit(axis){var found=plotted.find(function(item){return item.axis===axis&&valueUnit(item.item)});return found?valueUnit(found.item):''}
function baselineY(axis){var range=ranges[axis]||ranges.left,value=range.min<=0&&range.max>=0?0:range.min;return yAt(value,axis)}
function tickText(value,axis){var unit=axisUnit(axis);return formatNumber(value)+(unit?' '+unit:'')}
function stats(points){var values=(points||[]).map(function(point){return Number(point.value)}).filter(function(value){return Number.isFinite(value)});if(!values.length)return{last:null,min:null,max:null,average:null};var min=values[0],max=values[0],total=0;values.forEach(function(value){if(value<min)min=value;if(value>max)max=value;total+=value});return{last:values[values.length-1],min:min,max:max,average:total/values.length}}
var chartHtml;
if(!plotted.length)chartHtml='<div class="chart comparison-chart"><div class="chart-empty">'+esc(t('empty'))+'</div></div>';
else{
var svg=['<svg class="shd-chart" viewBox="0 0 '+width+' '+height+'" preserveAspectRatio="none" role="img" aria-label="'+esc(t('chartTitle'))+'"><g>'],leftTicks=ranges.left.binary?[0,.5,1]:[ranges.left.min,(ranges.left.min+ranges.left.max)/2,ranges.left.max];
leftTicks.forEach(function(value){var y=yAt(value,'left');svg.push('<line class="chart-grid-line" x1="'+left+'" x2="'+(width-right)+'" y1="'+y.toFixed(1)+'" y2="'+y.toFixed(1)+'"/><text class="chart-y-label" x="6" y="'+(y+3).toFixed(1)+'">'+esc(tickText(value,'left'))+'</text>')});
svg.push('</g><line class="chart-axis-line" x1="'+left+'" x2="'+left+'" y1="'+top+'" y2="'+(height-bottom)+'"/><line class="chart-axis-line" x1="'+left+'" x2="'+(width-right)+'" y1="'+(height-bottom)+'" y2="'+(height-bottom)+'"/>');
if(hasRight){var rightTicks=ranges.right.binary?[0,.5,1]:[ranges.right.min,(ranges.right.min+ranges.right.max)/2,ranges.right.max];svg.push('<line class="chart-axis-line" x1="'+(width-right)+'" x2="'+(width-right)+'" y1="'+top+'" y2="'+(height-bottom)+'"/>');rightTicks.forEach(function(value){var y=yAt(value,'right');svg.push('<text class="chart-y-label chart-y-label-right" text-anchor="end" x="'+(width-6)+'" y="'+(y+3).toFixed(1)+'">'+esc(tickText(value,'right'))+'</text>')})}
plotted.forEach(function(item){var points=item.points,path='M '+xAt(points[0],0,points).toFixed(1)+' '+yAt(points[0].value,item.axis).toFixed(1);for(var i=1;i<points.length;i++)path+=(ranges[item.axis].binary?' H '+xAt(points[i],i,points).toFixed(1)+' V '+yAt(points[i].value,item.axis).toFixed(1):' L '+xAt(points[i],i,points).toFixed(1)+' '+yAt(points[i].value,item.axis).toFixed(1));var info=comparisonLabel(item),summary=stats(item.raw);if(item.axis==='right'){var last=points[points.length-1],base=baselineY(item.axis),areaPath=path+' L '+xAt(last,points.length-1,points).toFixed(1)+' '+base.toFixed(1)+' L '+xAt(points[0],0,points).toFixed(1)+' '+base.toFixed(1)+' Z';svg.push('<path class="chart-area '+item.color+'" d="'+areaPath+'" aria-hidden="true"/>')}svg.push('<path class="chart-line '+item.color+'" d="'+path+'" aria-label="'+esc(info.label)+'"><title>'+esc(info.label+' · '+t('last')+': '+formatNumber(summary.last))+'</title></path>');if(points.length===1)svg.push('<circle class="chart-point '+item.color+'" cx="'+xAt(points[0],0,points).toFixed(1)+'" cy="'+yAt(points[0].value,item.axis).toFixed(1)+'" r="4"><title>'+esc(info.label+' · '+formatNumber(points[0].value))+'</title></circle>')});
if(timed&&start!==null){svg.push('<text class="chart-x-label" text-anchor="start" x="'+left+'" y="'+(height-10)+'">'+esc(chartTimeLabel(start))+'</text>');if(end!==start)svg.push('<text class="chart-x-label" text-anchor="end" x="'+(width-right)+'" y="'+(height-10)+'">'+esc(chartTimeLabel(end))+'</text>')}
chartHtml='<div class="chart comparison-chart">'+svg.join('')+'</svg></div>'
}
var html=[chartHtml,'<div class="chart-legend" role="list">'];
items.forEach(function(item){var info=comparisonLabel(item),summary=stats(item.raw),available=item.item.available!==false,error=!available?text(item.item.error_code||t('unavailable')):'';html.push('<div class="chart-legend-row" role="listitem"><span class="chart-swatch '+item.color+'" aria-hidden="true"></span><span class="chart-legend-name">'+esc(info.label)+(info.meta?'<small class="chart-legend-meta">'+esc(info.meta)+'</small>':'')+(error?'<small class="chart-legend-meta chart-legend-error">'+esc(error)+'</small>':'')+'</span><span class="chart-legend-stat"><span class="chart-legend-stat-label">'+esc(t('last'))+'</span>'+esc(formatNumber(summary.last))+'</span><span class="chart-legend-stat"><span class="chart-legend-stat-label">'+esc(t('minimum'))+'</span>'+esc(formatNumber(summary.min))+'</span><span class="chart-legend-stat"><span class="chart-legend-stat-label">'+esc(t('maximum'))+'</span>'+esc(formatNumber(summary.max))+'</span><span class="chart-legend-stat"><span class="chart-legend-stat-label">'+esc(t('average'))+'</span>'+esc(formatNumber(summary.average))+'</span></div>')});
return '<div class="comparison-view">'+html.join('')+'</div>'
}
function card(title,body,kind){var n=document.createElement('section');n.className='panel'+(kind?' '+kind:'');var h=document.createElement('h2');h.textContent=title;n.append(h);if(typeof body==='string')n.insertAdjacentHTML('beforeend',body);else if(body)n.append(body);return n}
function table(headers,rows){var wrap=document.createElement('div');wrap.className='table-wrap';var tab=document.createElement('table'),thead=document.createElement('thead'),tr=document.createElement('tr');headers.forEach(function(h){var th=document.createElement('th');th.textContent=h;tr.append(th)});thead.append(tr);tab.append(thead);var tb=document.createElement('tbody');(rows||[]).forEach(function(row){var r=document.createElement('tr');row.forEach(function(v){var td=document.createElement('td');if(v&&v.nodeType)td.append(v);else td.textContent=text(v);r.append(td)});tb.append(r)});tab.append(tb);wrap.append(tab);return wrap}
function empty(){var n=document.createElement('div');n.className='empty';n.textContent=t('empty');return n}
function metricGrid(items){var grid=document.createElement('div');grid.className='metric-grid';items.forEach(function(item){grid.append(card(item[0],'<p>'+esc(item[1])+'</p>','metric'))});return grid}
function renderHistory(d){var points=Array.isArray(d.points)?d.points:[];var sources=Array.isArray(d.history_sources)?d.history_sources:[];summary([{value:points.length+' '+t('points')},{value:t('source')+': '+text(d.selected_history_source||'—'),kind:d.selected_history_source?'good':'bad'},{value:t('records')+': '+((d.commands||[]).length)}]);var box=document.createElement('div');box.className='grid';box.append(card(t('source'),'<div class="source-list">'+sources.map(function(s){return '<div class="source-row"><span>'+esc(sourceLabel(s.name))+'</span><span>'+pill(s.available?t('available'):t('unavailable'),s.available?'good':'bad')+'</span></div>'}).join('')+'</div>'));var cp=card(t('value'));cp.insertAdjacentHTML('beforeend','<div class="chart">'+chart(points,'line-a')+'</div>');box.append(cp);el.content.append(box);if(points.length)el.content.append(table([t('time'),t('value'),t('name'),t('source')],points.slice(-80).reverse().map(function(p){return[dateText(p.time!==undefined?p.time:(p.source_timestamp!==undefined?p.source_timestamp:p.timestamp)),p.value,p.name||p.topic||p.channel,d.telemetry&&d.telemetry.provider||d.selected_history_source]})));else el.content.append(empty())}
function renderCompare(d){var list=Array.isArray(d.series)?d.series:[];summary([{value:seriesLabel(list.length)},{value:dateText(d.from)+' → '+dateText(d.to)}]);if(list.length)el.content.insertAdjacentHTML('beforeend',comparisonChart(list));else el.content.append(empty())}
function inputFor(s){var wrap=document.createElement('div');wrap.className='setting';var label=document.createElement('label');label.textContent=text(s.name||s.key);if(s.description){var small=document.createElement('small');small.textContent=s.description;label.append(small)}var value=s.current_value;var input;if(Array.isArray(s.enum_values)&&s.enum_values.length){input=document.createElement('select');s.enum_values.forEach(function(v){var o=document.createElement('option');o.value=String(v);o.textContent=String(v);input.append(o)});input.value=value===null||value===undefined?'':String(value)}else if(String(s.value_type||'').toLowerCase()==='boolean'){input=document.createElement('input');input.type='checkbox';input.checked=value===true||value===1||value==='1'}else{input=document.createElement('input');input.type=['number','integer'].includes(String(s.value_type||'').toLowerCase())?'number':'text';if(s.min!==null&&s.min!==undefined)input.min=s.min;if(s.max!==null&&s.max!==undefined)input.max=s.max;if(s.step!==null&&s.step!==undefined)input.step=s.step;input.value=value===null||value===undefined?'':String(value)}input.dataset.key=s.key;input.disabled=!s.writable;wrap.append(label,input);if(!s.writable){var ro=document.createElement('div');ro.className='muted';ro.textContent=t('readonly');wrap.append(ro)}return wrap}
function renderSettings(d){var settings=Array.isArray(d.settings)?d.settings:[];summary([{value:settings.length+' '+t('records')},{value:t('revision')+': '+text(d.source_revision)}]);var form=document.createElement('div');form.className='panel';settings.forEach(function(s){form.append(inputFor(s))});var actions=document.createElement('div');actions.className='actions';var btn=document.createElement('button');btn.type='button';btn.textContent=t('save');btn.disabled=!settings.some(function(s){return s.writable});btn.onclick=function(){saveSettings(form,settings,d)};actions.append(btn);form.append(actions);if(!settings.some(function(s){return s.writable})){var note=document.createElement('p');note.className='muted';note.textContent=t('noWritable');form.append(note)}el.content.append(form)}
function saveSettings(form,settings,d){var changes={};settings.forEach(function(s){if(!s.writable)return;var input=form.querySelector('[data-key="'+CSS.escape(String(s.key))+'"]');if(!input)return;var v=input.type==='checkbox'?input.checked:input.value;if(['number','integer'].includes(String(s.value_type||'').toLowerCase())&&v!=='')v=Number(v);changes[s.key]=v});if(!Object.keys(changes).length)return;var a=merge(state.config.sourceArgs,{settings:changes,expected_revision:d.source_revision||'',idempotency_key:'mcp-widget-'+Date.now()});rpc('tools/call',{name:state.config.writeTool||'shd_update_controller_expert_settings',arguments:a}).then(function(){setStatus(t('saved'),false);refresh()}).catch(function(e){setStatus(e.message||t('fail'),true)})}
function itemValue(item){if(Object.prototype.hasOwnProperty.call(item,'value'))return item.value;if(Object.prototype.hasOwnProperty.call(item,'current_value'))return item.current_value;var states=Array.isArray(item.states)?item.states:[];if(states.length===1)return states[0].value_present?states[0].value:null;return states.length?states.map(function(s){return text(s.value)}).join(', '):null}
function showProfile(item,d){var sourceArgs=obj(state.config.sourceArgs)?state.config.sourceArgs:{};var args={project:sourceArgs.project||d.project||'',controller:sourceArgs.controller||d.controller||'',device:String(item.id||item.device_id||''),include_live:true};if(!args.project||!args.controller||!args.device){setStatus(t('fail'),true);return}setStatus(t('loading'),false);rpc('tools/call',{name:state.config.detailTool||'shd_get_controller_device_profile',arguments:args}).then(function(result){var response=callData(result);var profile=obj(response.profile)?response.profile:response;var panel=card(t('profile'));panel.append(table([t('name'),t('value')],[[t('id'),profile.item_id||response.device||args.device],[t('name'),profile.user_name||profile.technical_name||profile.device_name],[t('room'),profile.room],[t('category'),profile.category],[t('type'),profile.type||profile.value_type],[t('access'),profile.access],[t('revision'),response.source_revision||profile.source_revision],[t('readback'),boolLabel(response.readback===true||profile.readback===true)]]));var states=Array.isArray(profile.states)?profile.states:[];if(states.length)panel.append(card(t('state'),table([t('name'),t('value'),t('unit'),t('quality'),t('readback')],states.map(function(s){return[s.name,s.value,s.unit,s.quality,boolLabel(s.readback)]}))));el.content.prepend(panel);setStatus('',false)}).catch(function(e){setStatus(e.message||t('fail'),true)})}
function renderDevices(d){var items=Array.isArray(d.items)?d.items:[];var total=Number.isFinite(Number(d.total))?d.total:items.length;summary([{value:total+' '+t('deviceCount')},{value:t('source')+': '+sourceLabel(d.source_type||d.source),kind:d.inventory_available?'good':'bad'},{value:t('revision')+': '+text(d.inventory_revision||d.revision)}]);if(!items.length){el.content.append(empty());return}var rows=items.map(function(item){var detail=document.createElement('button');detail.type='button';detail.className='inline-action';detail.textContent=t('details');detail.onclick=function(){showProfile(item,d)};return[item.id,item.name||item.title||item.device_name,item.room,item.category,item.type||item.value_type,item.access,itemValue(item),detail]});el.content.append(table([t('id'),t('name'),t('room'),t('category'),t('type'),t('access'),t('value'),t('details')],rows))}
function liveRows(d){var rows=[];var controllers=Array.isArray(d.controllers)?d.controllers:[];var names={};controllers.forEach(function(c){names[String(c.controller||c.source_controller_id||'')]=c.controller_name||c.controller||c.source_controller_id});(Array.isArray(d.states)?d.states:[]).forEach(function(s){rows.push([names[String(s.source_controller_id||'')]||s.source_controller_id,s.item_id,s.name,s.value_present?s.value:null,s.unit,s.quality,formatFreshness(s.freshness_ms),boolLabel(s.readback),s.unavailable_reason])});return rows}
function renderLiveState(d){var rows=liveRows(d);var controllers=Array.isArray(d.controllers)?d.controllers:[];var controllerCount=d.controller_count||controllers.length||new Set(rows.map(function(r){return r[0]})).size;summary([{value:controllerCount+' '+t('controllerCount')},{value:(d.state_count||rows.length)+' '+t('stateCount')},{value:t('readback')+': '+boolLabel(d.readback)}]);el.content.append(rows.length?table([t('controller'),t('id'),t('name'),t('value'),t('unit'),t('quality'),t('freshness'),t('readback'),t('unavailableReason')],rows):empty())}
function renderStatistics(d){if(Array.isArray(d.series)){renderStatisticsCollection(d);return}var s=obj(d.summary)?d.summary:{};var available=d.statistics_available===true;var scope=scopeLabel(d),sourcePill=statisticsSourcePill(d),summaryItems=[];if(scope)summaryItems.push({value:scope});summaryItems.push({value:kindLabel(d.statistics_kind)},{value:sampleLabel(s.sample_count||0)});if(sourcePill)summaryItems.push(sourcePill);summaryItems.push({value:dateText(d.from)+' → '+dateText(d.to)});summary(summaryItems);if(!available){var unavailable=card(t('status'));unavailable.append(document.createTextNode(t('noStats')+' '+text(d.statistics_error_code||'')));el.content.append(unavailable)}else if(d.statistics_kind==='binary'){el.content.append(metricGrid([[t('events'),formatNumber(s.event_count)], [t('transitions'),formatNumber(s.transition_count)], [t('periods'),formatNumber(s.period_count)], [t('activeTime'),formatDuration(s.active_seconds)], [t('activeShare'),formatNumber(s.active_percent)+'%'], [t('coverage'),formatDuration(s.coverage_seconds)], [t('averageDuration'),formatDuration(s.average_duration_seconds)], [t('medianDuration'),formatDuration(s.median_duration_seconds)], [t('shortest'),formatDuration(s.min_duration_seconds)], [t('longest'),formatDuration(s.max_duration_seconds)]]))}else if(d.statistics_kind==='numeric'){el.content.append(metricGrid([[t('samples'),formatNumber(s.sample_count)], [t('minimum'),formatNumber(s.min)], [t('maximum'),formatNumber(s.max)], [t('average'),formatNumber(s.average)], [t('median'),formatNumber(s.median)]]))}var meta=card(t('sourceDetails'));meta.append(table([t('source'),t('kind'),t('quality'),t('unit'),t('start'),t('end')],[[sourceLabel(d.selected_history_source||d.history_source_requested),kindLabel(d.statistics_kind),d.quality,d.value_unit||'—',dateText(d.from),dateText(d.to)]]));el.content.append(meta);var intervals=Array.isArray(d.intervals)?d.intervals:[];if(intervals.length){el.content.append(card(t('intervals'),table([t('start'),t('end'),t('duration'),t('status')],intervals.map(function(i){return[dateText(i.start),dateText(i.end),formatDuration(i.duration_seconds),i.open?t('open'):t('checked')]}))))}var buckets=Array.isArray(d.buckets)?d.buckets:[];if(buckets.length){var headers=d.statistics_kind==='binary'?[t('bucket'),t('events'),t('transitions'),t('periods'),t('activeTime')]:[t('bucket'),t('samples'),t('minimum'),t('maximum'),t('average')];el.content.append(card(t('hourly'),table(headers,buckets.map(function(b){return d.statistics_kind==='binary'?[dateText(b.bucket_start),formatNumber(b.event_count),formatNumber(b.transition_count),formatNumber(b.period_count),formatDuration(b.active_seconds)]:[dateText(b.bucket_start),formatNumber(b.sample_count),formatNumber(b.min),formatNumber(b.max),formatNumber(b.average)]}))));}var points=Array.isArray(d.points)?d.points:[];if(points.length)el.content.append(card(t('points'),table([t('time'),t('value'),t('quality')],points.slice(-80).reverse().map(function(p){return[dateText(p.time!==undefined?p.time:p.source_timestamp),p.value,p.quality]}))))}
function renderTopology(d){var controllers=Array.isArray(d.controllers)?d.controllers:[];summary([{value:controllers.length+' '+t('controllerCount')},{value:t('inventorySeparate')}]);el.content.append(controllers.length?table([t('controller'),t('type'),t('revision')],controllers.map(function(c){var role=c.controller_role==='source'?t('source'):t('controller');return[c.controller_name||c.controller,role+' · '+(c.controller_type||'—'),c.source_revision]})):empty())}
function renderHealth(d){var list=Array.isArray(d.controllers)?d.controllers:[d];summary([{value:list.length+' '+t('controllerCount')}]);var grid=document.createElement('div');grid.className='grid';list.forEach(function(c){var sources=Array.isArray(c.sources)?c.sources:[];grid.append(card(c.controller||c.source_controller_id||t('controller'),'<p>'+pill(c.status||'—',c.status==='available'?'good':'')+'</p><div class="source-list">'+sources.map(function(s){return '<div class="source-row"><span>'+esc(sourceLabel(s.name))+'</span><span>'+pill(s.available?t('available'):(s.configured?t('configured'):t('unavailable')),s.available?'good':(s.configured?'':'bad'))+'</span></div>'}).join('')+'</div>'))});el.content.append(grid)}
function renderAudit(d){var rows=Array.isArray(d.audit)?d.audit:(Array.isArray(d.commands)?d.commands:[]);summary([{value:rows.length+' '+t('records')}]);el.content.append(rows.length?table([t('time'),t('device'),t('action'),t('status'),t('requested'),t('actual')],rows.map(function(x){return[dateText(x.created_at),x.item_id,x.action,x.status,text(x.requested_value),text(x.actual_readback)]})):empty())}
function render(){clear();labels();var d=state.data||{};try{if(state.mode==='history')renderHistory(d);else if(state.mode==='compare')renderCompare(d);else if(state.mode==='settings')renderSettings(d);else if(state.mode==='topology')renderTopology(d);else if(state.mode==='devices')renderDevices(d);else if(state.mode==='live-state')renderLiveState(d);else if(state.mode==='statistics')renderStatistics(d);else if(state.mode==='health')renderHealth(d);else if(state.mode==='audit')renderAudit(d);else el.content.append(document.createTextNode(t('unknown')))}catch(e){setStatus(e.message||t('unknown'),true)}}
function apply(x){var p=unwrap(x);state.data=dataFrom(p);setStatus('',false);render()}
function refresh(){if(state.busy)return;var args=merge(state.config.listArgs,state.config.sourceArgs);if(el.source.value&&['history','compare','health','statistics'].includes(state.mode))args.source=el.source.value;var tool=state.config.dataTool;if(!tool){setStatus(t('unknown'),true);return}state.busy=true;el.refresh.disabled=true;setStatus(t('loading'),false);rpc('tools/call',{name:tool,arguments:args}).then(apply).catch(function(e){setStatus(e.message||t('fail'),true)}).finally(function(){state.busy=false;el.refresh.disabled=false})}
labels();el.source.onchange=refresh;el.refresh.onclick=refresh;
window.addEventListener('message',function(ev){if(ev.source!==window.parent)return;var m=ev.data||{};if(m.id!==undefined&&pending.has(m.id)){var q=pending.get(m.id);pending.delete(m.id);clearTimeout(q.timeout);if(m.error)q.reject(Error(m.error.message||t('fail')));else q.resolve(m.result);return}if(m.method==='ui/notifications/tool-input'||m.method==='ui/notifications/tool-result'){try{apply(m.params&&m.params.structuredContent||m.params||{})}catch(e){setStatus(e.message||t('fail'),true)}}});
rpc('ui/initialize',{protocolVersion:'2026-01-26',appInfo:{name:'SHD controller observability',title:'SHD MCP',version:'1.0.0',websiteUrl:'https://shd.xyz.su'},appCapabilities:{}}).then(function(){window.parent.postMessage({jsonrpc:'2.0',method:'ui/notifications/initialized',params:{}},'*')}).catch(function(){});var initial=window.openai&&window.openai.toolOutput||window.openai&&window.openai.toolInput;if(initial){try{apply(initial)}catch(e){setStatus(e.message||t('fail'),true)}}else{refresh()}
}());
</script>
</body>
</html>