Unify MCP widgets and complete localization

This commit is contained in:
2026-09-13 17:23:32 +03:00
parent 0ba42afe35
commit 26692fad1c
151 changed files with 3509 additions and 3050 deletions
+8
View File
@@ -46,6 +46,13 @@ The expected flow is:
3. The selected widget renders a register, can call the matching detail tool for 3. The selected widget renders a register, can call the matching detail tool for
one selected row and exposes controlled write actions when declared by the one selected row and exposes controlled write actions when declared by the
render metadata. render metadata.
4. For controller requests, use the explicit controller data→render pair:
`shd_get_controller_source_inventory``shd_render_controller_devices_widget`,
`shd_get_home_live_state``shd_render_controller_live_state_widget`,
`shd_get_controller_source_history``shd_render_controller_history_widget`,
or `shd_get_controller_state_statistics`
`shd_render_controller_statistics_widget`. Pass the complete data result and
preserve `project`, `controller` and `item_id` in `meta.sourceArgs`.
The widget resources are versioned at `ui://shd/active-projects/v1.html`, The widget resources are versioned at `ui://shd/active-projects/v1.html`,
`ui://shd/documents/v1.html`, `ui://shd/tasks/v1.html`, `ui://shd/documents/v1.html`, `ui://shd/tasks/v1.html`,
@@ -53,6 +60,7 @@ The widget resources are versioned at `ui://shd/active-projects/v1.html`,
`ui://shd/discussions/v1.html`, `ui://shd/notifications/v1.html`, `ui://shd/discussions/v1.html`, `ui://shd/notifications/v1.html`,
`ui://shd/scheduling/v1.html`, `ui://shd/inventory/v1.html`, `ui://shd/scheduling/v1.html`, `ui://shd/inventory/v1.html`,
`ui://shd/agents/v1.html`, `ui://shd/status-page/v1.html`, `ui://shd/agents/v1.html`, `ui://shd/status-page/v1.html`,
`ui://shd/controller-observability/v1.html`,
`ui://shd/project-db/v1.html`, `ui://shd/notes/v1.html`, `ui://shd/project-db/v1.html`, `ui://shd/notes/v1.html`,
`ui://shd/project-overview/v1.html`, `ui://shd/files/v1.html`, `ui://shd/project-overview/v1.html`, `ui://shd/files/v1.html`,
`ui://shd/proposals/v1.html`, `ui://shd/terms/v1.html`, `ui://shd/proposals/v1.html`, `ui://shd/terms/v1.html`,
+7 -3
View File
@@ -61,11 +61,15 @@ Finance, CRM, Discussions, Notifications, Scheduling, Inventory, Agents,
Status Page, Project DB, Notes, Project overview, Files, Proposals, Terms, Status Page, Project DB, Notes, Project overview, Files, Proposals, Terms,
Activity, Organizations and access, Gitea Issues and Schemes: Activity, Organizations and access, Gitea Issues and Schemes:
Controller observability uses one parameterized resource for source history, Controller observability uses one parameterized resource for device inventory,
cross-controller comparisons, expert settings, complete project topology, current state, server-computed statistics, source history, cross-controller
telemetry health and source audit: comparisons, expert settings, complete project topology, telemetry health and
source audit:
- `shd_render_controller_history_widget``ui://shd/controller-observability/v1.html`; - `shd_render_controller_history_widget``ui://shd/controller-observability/v1.html`;
- `shd_render_controller_statistics_widget``ui://shd/controller-observability/v1.html`;
- `shd_render_controller_devices_widget``ui://shd/controller-observability/v1.html`;
- `shd_render_controller_live_state_widget``ui://shd/controller-observability/v1.html`;
- `shd_render_controller_compare_widget``ui://shd/controller-observability/v1.html`; - `shd_render_controller_compare_widget``ui://shd/controller-observability/v1.html`;
- `shd_render_controller_settings_widget``ui://shd/controller-observability/v1.html`; - `shd_render_controller_settings_widget``ui://shd/controller-observability/v1.html`;
- `shd_render_controller_topology_widget``ui://shd/controller-observability/v1.html`; - `shd_render_controller_topology_widget``ui://shd/controller-observability/v1.html`;
+21 -9
View File
@@ -33,7 +33,7 @@ modules. Skills do not grant permissions or bypass server-side ACLs.
- linked Gitea issues, labels, comments and attachments; - linked Gitea issues, labels, comments and attachments;
- safe bulk-change planning and validation rules. - safe bulk-change planning and validation rules.
When the connected MCP host supports MCP Apps UI, the 42 SHD render tools have When the connected MCP host supports MCP Apps UI, the 44 SHD render tools have
inline interactive widgets: active projects, module registers and specialized inline interactive widgets: active projects, module registers and specialized
Kanban, workload, timeline, funnel, dashboard, calendar, matrix, tree and Kanban, workload, timeline, funnel, dashboard, calendar, matrix, tree and
Project DB views. Each widget sorts or filters normalized records, shows Project DB views. Each widget sorts or filters normalized records, shows
@@ -46,19 +46,31 @@ The same tools still return normal structured data for hosts that do not render
widgets. widgets.
The controller observability render tools share the versioned The controller observability render tools share the versioned
`widgets/controller-observability/v1/` resource. It exposes explicit native vs `widgets/controller-observability/v1/` resource. It exposes controller device
Influx/Grafana provenance, universal comparison presets, complete controller inventory and profiles, normalized live states, server-computed binary/numeric
topology, telemetry health and operation audit. Expert-setting controls are enabled only statistics, explicit native vs Influx/Grafana provenance, universal comparison
for source-provided writable descriptors. presets, complete controller topology, telemetry health and operation audit.
Expert-setting controls are enabled only for source-provided writable descriptors.
The active-projects widget is versioned in `widgets/active-projects/v1/` with a The active-projects widget is versioned in `widgets/active-projects/v1/` with a
manifest, SHA256 checksum and source/provenance note. The SHD MCP backend reads manifest, SHA256 checksum and source/provenance note. The SHD MCP backend reads
that artifact for the `ui://shd/active-projects/v1.html` resource. that artifact for the `ui://shd/active-projects/v1.html` resource.
The other versioned resources are stored under `widgets/` with a manifest, The standard module resources are generated from the single
SHA256 checksum and source/provenance note for each resource. Their render `widgets/_shared/universal-widget.html` source by
tools are deliberately separate from the list tools so the server can keep `scripts/generate-widget-artifacts.mjs`. Each generated artifact still keeps
data access and UI rendering auditable. its own URI, manifest, SHA256 checksum and provenance note: this preserves
resource cache keys and module-scope registration while removing duplicated
HTML, bridge and action runtime code. The generator supports `--check` for a
no-write consistency check. Module copy and fallback tool configuration lives
in the sibling `widgets/_shared/universal-widget-config.json` source and is
injected into each standalone artifact by the generator. For an older template
that still contains the inline configuration block, run `--extract-config`
once and then `--migrate-template` once. The
active-projects and controller-observability resources remain separate because
they are specialized applications with different interaction contracts. Render
tools are deliberately separate from list tools so data access and UI rendering
remain auditable.
## MCP connection ## MCP connection
@@ -17,6 +17,23 @@ configuration.
- Keep diagnostics, configuration, endpoint publication and network routing as - Keep diagnostics, configuration, endpoint publication and network routing as
separate operations. Never expose credentials, private keys or tunnel data. separate operations. Never expose credentials, private keys or tunnel data.
## Controller data and widgets
- For a controller device list, call `shd_get_controller_source_inventory`, then
`shd_render_controller_devices_widget` with the complete result and scoped
`meta.sourceArgs`.
- For current states across controllers, call `shd_get_home_live_state`, then
`shd_render_controller_live_state_widget`.
- For one presence, binary state or numeric sensor statistics, call
`shd_get_controller_state_statistics`, then
`shd_render_controller_statistics_widget`. The server result is authoritative
for counts, intervals, durations, percentages, medians and hourly buckets;
do not calculate them from raw history in the model.
- For raw time-series history, call `shd_get_controller_source_history`, then
`shd_render_controller_history_widget`. Keep the detailed rows in the widget
after a successful render and use plain text only when the UI call is
unavailable or fails.
## High-impact actions ## High-impact actions
Creating/closing tunnels, publishing or revoking endpoints, replacing access Creating/closing tunnels, publishing or revoking endpoints, replacing access
@@ -24,6 +24,7 @@ readback. Do not invent fields, statuses, identifiers, routes or capabilities.
- Notifications, preferences or read state: use Notifications tools and the `shd-notifications` workflow. - Notifications, preferences or read state: use Notifications tools and the `shd-notifications` workflow.
- Inventory, stock, assets, procurement or stocktakes: use Inventory tools and the `shd-inventory` workflow. - Inventory, stock, assets, procurement or stocktakes: use Inventory tools and the `shd-inventory` workflow.
- Agents, controllers, diagnostics, tunnels or network access: use Agents tools and the `shd-agents-operations` workflow. - Agents, controllers, diagnostics, tunnels or network access: use Agents tools and the `shd-agents-operations` workflow.
- Controller devices, current states, presence/history or sensor statistics: use the controller-integrations source tools; in a UI-capable host call the matching controller render tool after the data tool and before the final response (`shd_render_controller_devices_widget`, `shd_render_controller_live_state_widget`, `shd_render_controller_history_widget` or `shd_render_controller_statistics_widget`).
- Status-page monitors, history, groups or incidents: use Status Page tools and the `shd-status-page` workflow. - Status-page monitors, history, groups or incidents: use Status Page tools and the `shd-status-page` workflow.
- Realtime sessions, event cursors or historical activity: use Realtime tools and the `shd-realtime-activity` workflow. - Realtime sessions, event cursors or historical activity: use Realtime tools and the `shd-realtime-activity` workflow.
- Terms documents, revisions, completeness or contract discussions: use Terms tools and the `shd-terms-contracts` workflow. - Terms documents, revisions, completeness or contract discussions: use Terms tools and the `shd-terms-contracts` workflow.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,558 @@
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SHD MCP</title>
<style>
:root{color-scheme:light dark;--bg:#f8fafc;--surface:#e8edf2;--hover:#e1e8ef;--line:rgba(31,41,55,.16);--ink:#1d1f25;--muted:#616670;--accent:#535963;--soft:rgba(75,85,99,.12);--bad:#b42318;--good:#2f855a;--button:#fff;--shadow:0 10px 26px rgba(31,41,55,.1)}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.shell{max-width:980px;margin:auto;padding:22px}.card{padding:20px;border:1px solid var(--line);border-radius:18px;background:var(--surface);box-shadow:var(--shadow)}
.eyebrow{color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase}h1{margin:4px 0 5px;font-size:25px;line-height:1.15}.lede{margin:0;color:var(--muted)}.summary{display:flex;gap:8px;flex-wrap:wrap;margin:18px 0 14px}.summary span{padding:5px 9px;border:1px solid var(--line);border-radius:999px;background:var(--hover);color:var(--muted)}.summary strong{color:var(--ink)}
.toolbar{display:flex;gap:8px;margin-bottom:10px}.search{min-width:0;flex:1;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--bg);color:inherit;outline:0}.search:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--soft)}button{cursor:pointer}.refresh{padding:10px 14px;border:1px solid var(--accent);border-radius:10px;background:var(--accent);color:var(--button);font-weight:650}.refresh:disabled{opacity:.6;cursor:wait}.status{min-height:22px;color:var(--muted)}.error{color:var(--bad)}
.table-wrap{max-height:460px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--bg)}table{width:100%;border-collapse:collapse;min-width:560px}th,td{padding:11px 13px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}th{position:sticky;top:0;background:var(--hover);color:var(--muted);font-size:12px;font-weight:650;z-index:1}tbody tr:last-child td{border-bottom:0}.record{cursor:pointer;outline:0}.record:hover,.record:focus{background:var(--soft)}.title{font-weight:650}.sub{display:block;color:var(--muted);font-size:12px;margin-top:2px}.pill{display:inline-block;padding:3px 8px;border-radius:999px;background:var(--soft);color:var(--accent);font-size:12px;white-space:nowrap}.pill.up,.pill.ok,.pill.active{color:var(--good);background:rgba(47,133,90,.14)}.pill.down,.pill.error,.pill.overdue{color:var(--bad);background:rgba(180,35,24,.12)}.empty{padding:24px;text-align:center;color:var(--muted)}
.visual[hidden]{display:none}.visual{max-height:460px;overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--bg);padding:12px}.visual-card{display:block;width:100%;padding:11px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface);color:inherit;text-align:left;cursor:pointer}.visual-card:hover,.visual-card:focus{background:var(--hover);border-color:var(--accent);outline:0}.visual-title{display:block;font-weight:650}.visual-date,.visual-value{display:block;color:var(--muted);font-size:12px;margin-top:4px}.visual-line{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:7px}.kanban{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(190px,1fr);gap:10px;overflow-x:auto;align-items:start}.kanban-col{min-width:190px;padding:10px;border:1px solid var(--line);border-radius:12px;background:var(--surface)}.kanban-head{display:flex;justify-content:space-between;gap:8px;margin-bottom:8px;font-weight:650}.kanban-list{display:grid;gap:8px}.metric-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(145px,1fr));gap:10px;margin-bottom:12px}.metric{padding:12px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.metric-label{display:block;color:var(--muted);font-size:12px}.metric-value{display:block;font-size:22px;font-weight:700;margin-top:4px}.timeline{display:grid;gap:0}.timeline-item{display:grid;grid-template-columns:18px minmax(0,1fr);gap:10px;position:relative;padding-bottom:10px}.timeline-rail{position:absolute;left:8px;top:16px;bottom:0;width:2px;background:var(--line)}.timeline-item:last-child .timeline-rail{display:none}.timeline-dot{width:16px;height:16px;border-radius:50%;background:var(--accent);border:3px solid var(--soft);z-index:1}.timeline-content{min-width:0}.timeline-meta{color:var(--muted);font-size:12px;margin-bottom:5px}.funnel{display:grid;gap:9px}.funnel-row{display:grid;grid-template-columns:minmax(100px,1fr) minmax(120px,2fr) auto;gap:10px;align-items:center}.funnel-fill{width:100%;height:10px;accent-color:var(--accent)}.progress-card{display:grid;gap:7px;padding:11px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.progress{width:100%;height:10px;accent-color:var(--accent)}.calendar{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px}.calendar-day{padding:10px;border:1px solid var(--line);border-radius:12px;background:var(--surface);display:grid;gap:8px;align-content:start}.calendar-head{font-weight:650}.matrix-wrap{overflow:auto}.matrix-table{width:100%;border-collapse:collapse;min-width:560px}.matrix-table th,.matrix-table td{padding:9px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.matrix-table th{color:var(--muted);font-size:12px;background:var(--hover)}.tree{display:grid;gap:7px}.tree-children{display:grid;gap:7px;padding-left:18px;border-left:1px solid var(--line)}.preview-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}.preview-card{padding:12px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}.preview-fields{display:grid;gap:5px;margin-top:8px}.preview-field{font-size:12px;overflow-wrap:anywhere}.preview-field strong{color:var(--muted);font-weight:500}@media(max-width:640px){.funnel-row{grid-template-columns:1fr auto}.funnel-fill{grid-column:1/-1}}
.details{margin-top:14px;padding:16px;border:1px solid var(--line);border-radius:12px;background:var(--bg)}.details[hidden]{display:none}.detail-head{display:flex;justify-content:space-between;gap:12px;align-items:start}.details h2{margin:0;font-size:18px}.close{border:0;background:transparent;color:var(--muted);font-size:20px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:14px}.detail{padding:9px;border-radius:8px;background:var(--surface)}.label{display:block;color:var(--muted);font-size:11px;margin-bottom:3px}.value{overflow-wrap:anywhere;white-space:pre-wrap}@media(max-width:640px){.shell{padding:12px}.card{padding:15px}.toolbar{display:grid;grid-template-columns:minmax(0,1fr) auto}}
@media(prefers-color-scheme:dark){:root{--bg:#202020;--surface:#292929;--hover:#303336;--line:#43474c;--ink:#eaecf0;--muted:#96999d;--accent:#c7b07a;--soft:rgba(199,176,122,.18);--bad:#ff9a93;--good:#83c995;--button:#241f14;--shadow:0 10px 26px rgba(0,0,0,.34)}}
</style>
<style data-shd-widget-write-actions="css">
.shd-widget-actions{margin-top:14px;padding:14px;border:1px solid var(--border,var(--line,rgba(31,41,55,.16)));border-radius:11px;background:var(--surface,var(--panel,#e8edf2));color:var(--text,var(--ink,#1d1f25))}
.shd-widget-actions[hidden]{display:none}
.shd-widget-actions-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
.shd-widget-actions-selection{overflow:hidden;color:var(--muted,#616670);font-size:12px;text-overflow:ellipsis;white-space:nowrap}
.shd-widget-actions-select,.shd-widget-actions-input{width:100%;border:1px solid var(--border,var(--line,rgba(31,41,55,.16)));border-radius:8px;padding:8px 10px;background:var(--bg,#f8fafc);color:var(--text,var(--ink,#1d1f25))}
.shd-widget-actions-select:focus,.shd-widget-actions-input:focus{border-color:var(--accent,#535963);outline:0;box-shadow:0 0 0 3px var(--accent-soft,var(--soft,rgba(75,85,99,.12)))}
.shd-widget-actions-form{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-top:10px}
.shd-widget-actions-field{display:grid;gap:4px;color:var(--muted,#616670);font-size:11px}
.shd-widget-actions-field:has(textarea){grid-column:span 2}
.shd-widget-actions-field input[type=checkbox]{width:18px;height:18px;accent-color:var(--accent,#535963)}
.shd-widget-actions-confirm{display:flex;grid-column:1/-1;align-items:center;gap:7px;color:var(--text,var(--ink,#1d1f25));font-size:12px}
.shd-widget-actions-warning,.shd-widget-actions-hint{grid-column:1/-1;color:var(--muted,#616670);font-size:12px}
.shd-widget-actions-danger{color:var(--danger,var(--bad,#b42318))}
.shd-widget-actions-status{min-height:18px;margin-top:9px;color:var(--muted,#616670);font-size:12px}
.shd-widget-actions-status-error{color:var(--danger,var(--bad,#b42318))}
.shd-widget-actions-buttons{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}
.shd-widget-actions-buttons button{border:1px solid var(--border,var(--line,rgba(31,41,55,.16)));border-radius:8px;padding:8px 11px;background:var(--bg,#f8fafc);color:var(--text,var(--ink,#1d1f25));cursor:pointer}
.shd-widget-actions-buttons button:hover{background:var(--surface-hover,var(--hover,#e1e8ef))}
.shd-widget-actions-buttons button:disabled{cursor:wait;opacity:.62}
@media(max-width:600px){.shd-widget-actions-field:has(textarea){grid-column:auto}.shd-widget-actions-header{display:block}.shd-widget-actions-selection{display:block;margin-top:3px}}
</style>
</head>
<body>
<main class="shell" data-shd-widget-engine="shd-universal-v1" data-widget-module="__SHD_WIDGET_MODULE__"><section class="card">
<div class="eyebrow">SHD MCP</div><h1 id="title">SHD</h1><p class="lede" id="desc"></p><div class="summary" id="summary"></div>
<div class="toolbar"><input class="search" id="search" type="search" autocomplete="off"><button class="refresh" id="refresh" type="button"></button></div>
<div class="status" id="status" role="status" aria-live="polite"></div>
<div class="table-wrap"><table><thead><tr><th id="head-title"></th><th id="head-status"></th><th id="head-date"></th><th id="head-value"></th></tr></thead><tbody id="rows"></tbody></table><div class="empty" id="empty" hidden></div></div><div class="visual" id="visual" hidden></div>
<section class="details" id="details" hidden aria-live="polite"><div class="detail-head"><div><h2 id="detail-title"></h2><div class="sub" id="detail-sub"></div></div><button class="close" id="close" type="button">×</button></div><div class="grid" id="grid"></div></section>
</section></main>
<script>
(function(){
'use strict';
var DEFAULT_MODULE='__SHD_WIDGET_MODULE__';
var C={ru:{refresh:'Обновить',search:'Поиск по названию, статусу или ID',loading:'Загрузка…',empty:'Нет данных для отображения.',records:'записей',status:'Статус',date:'Дата',value:'Детали',id:'ID',details:'Детали',close:'Закрыть',load:'Не удалось загрузить данные.',detail:'Не удалось загрузить детали.',context:'Для обновления нужен контекст источника',total:'Всего',amount:'Сумма',planned:'План',capacity:'Ёмкость',utilization:'Загрузка',overload:'Перегрузка',warnings:'Предупреждения',complete:'Готово',pending:'Ожидают',approved:'Согласовано',rejected:'Отклонено',progress:'Прогресс',score:'Оценка',member:'Участник',role:'Роль',permissions:'Разрешения',preview:'Предпросмотр',noWarnings:'Предупреждений нет.',health:'Состояние',yes:'Да',no:'Нет'},en:{refresh:'Refresh',search:'Search by name, status or ID',loading:'Loading…',empty:'No data to display.',records:'records',status:'Status',date:'Date',value:'Details',id:'ID',details:'Details',close:'Close',load:'Could not load data.',detail:'Could not load details.',context:'A source context is required to refresh.',total:'Total',amount:'Amount',planned:'Planned',capacity:'Capacity',utilization:'Utilization',overload:'Overload',warnings:'Warnings',complete:'Complete',pending:'Pending',approved:'Approved',rejected:'Rejected',progress:'Progress',score:'Score',member:'Member',role:'Role',permissions:'Permissions',preview:'Preview',noWarnings:'No warnings.',health:'Health',yes:'Yes',no:'No'}};
var FIELD_LABELS={ru:{id:'ID',key:'Ключ',project_id:'ID проекта',task_project_id:'ID проекта задачи',linked_project_ids:'Связанные проекты',project_code:'Код проекта',base_reference:'Ссылка на базу',base_public_id:'Публичный ID базы',table_public_id:'Публичный ID таблицы',record_public_id:'Публичный ID записи',title:'Название',name:'Название',description:'Описание',status:'Статус',status_id:'ID статуса',is_done:'Завершено',is_complete:'Завершено',section:'Раздел',sort_order:'Порядок сортировки',created_at:'Создано',updated_at:'Обновлено',due_date:'Срок',deadline:'Дедлайн',starts_at:'Начало',ends_at:'Окончание',url:'Ссылка',diagnostic:'Диагностика',message:'Сообщение',type:'Тип',state:'Состояние',phase:'Этап',health:'Состояние',email:'Электронная почта',username:'Имя пользователя',role:'Роль',permissions:'Разрешения',source:'Источник',module:'Модуль',table:'Таблица',fields:'Поля',value:'Значение',stage:'Стадия',approval_status:'Статус согласования',approval_state:'Состояние согласования',priority:'Приоритет',assignee:'Исполнитель',owner:'Владелец',type:'Тип',category:'Категория',permissions:'Разрешения',permission:'Разрешение',read:'Чтение',write:'Запись',manage:'Управление',admin:'Администратор',view:'Просмотр',create:'Создание',update:'Изменение',delete:'Удаление',error:'Ошибка',warning:'Предупреждение',message:'Сообщение',path:'Путь',link:'Ссылка',amount:'Сумма',total:'Итого',quantity:'Количество',available_quantity:'Доступное количество',stock:'Остаток',planned:'План',planned_hours:'Плановые часы',capacity:'Ёмкость',capacity_hours:'Часы ёмкости',utilization:'Загрузка',utilization_percent:'Загрузка, %',overload:'Перегрузка',overload_hours:'Часы перегрузки',score:'Оценка',progress:'Прогресс',completeness_score:'Оценка полноты',completion_percent:'Полнота, %',availability_percent:'Доступность, %',record_count:'Количество записей',revision:'Ревизия',pages:'Страницы',page_count:'Количество страниц',date:'Дата',is_archived:'В архиве',low_stock:'Мало на складе',critical:'Критический',meta:'Метаданные'},en:{id:'ID',key:'Key',project_id:'Project ID',task_project_id:'Task project ID',linked_project_ids:'Linked projects',project_code:'Project code',base_reference:'Base reference',base_public_id:'Public base ID',table_public_id:'Public table ID',record_public_id:'Public record ID',title:'Title',name:'Name',description:'Description',status:'Status',status_id:'Status ID',is_done:'Completed',is_complete:'Completed',section:'Section',sort_order:'Sort order',created_at:'Created',updated_at:'Updated',due_date:'Due date',deadline:'Deadline',starts_at:'Starts',ends_at:'Ends',url:'URL',diagnostic:'Diagnostics',message:'Message',type:'Type',state:'State',phase:'Phase',health:'Health',email:'Email',username:'Username',role:'Role',permissions:'Permissions',source:'Source',module:'Module',table:'Table',fields:'Fields',value:'Value',stage:'Stage',approval_status:'Approval status',approval_state:'Approval state',priority:'Priority',assignee:'Assignee',owner:'Owner',type:'Type',category:'Category',permissions:'Permissions',permission:'Permission',read:'Read',write:'Write',manage:'Manage',admin:'Admin',view:'View',create:'Create',update:'Update',delete:'Delete',error:'Error',warning:'Warning',message:'Message',path:'Path',link:'Link',amount:'Amount',total:'Total',quantity:'Quantity',available_quantity:'Available quantity',stock:'Stock',planned:'Planned',planned_hours:'Planned hours',capacity:'Capacity',capacity_hours:'Capacity hours',utilization:'Utilization',utilization_percent:'Utilization, %',overload:'Overload',overload_hours:'Overload hours',score:'Score',progress:'Progress',completeness_score:'Completeness score',completion_percent:'Completeness, %',availability_percent:'Availability, %',record_count:'Record count',revision:'Revision',pages:'Pages',page_count:'Page count',date:'Date',is_archived:'Archived',low_stock:'Low stock',critical:'Critical',meta:'Metadata'}};
var VALUE_LABELS={ru:{Tasks_Status_Todo:'К выполнению',Tasks_Status_Done:'Завершено',Tasks_Status_In_Progress:'В работе',Tasks_Status_Review:'На проверке',Modules_Done:'Завершено',todo:'К выполнению',to_do:'К выполнению','to do':'К выполнению',in_progress:'В работе','in progress':'В работе','in-progress':'В работе',in_progress_status:'В работе',done:'Завершено',completed:'Завершено',complete:'Завершено',closed:'Закрыто',active:'Активно',pending:'Ожидает',review:'На проверке',approved:'Согласовано',rejected:'Отклонено',archived:'В архиве',not_set:'Не задано'},en:{Tasks_Status_Todo:'To do',Tasks_Status_Done:'Done',Tasks_Status_In_Progress:'In progress',Tasks_Status_Review:'In review',Modules_Done:'Done',todo:'To do',to_do:'To do','to do':'To do',in_progress:'In progress','in progress':'In progress','in-progress':'In progress',in_progress_status:'In progress',done:'Done',completed:'Completed',complete:'Complete',closed:'Closed',active:'Active',pending:'Pending',review:'In review',approved:'Approved',rejected:'Rejected',archived:'Archived',not_set:'Not set'}};
var MODULE_CONFIG=__SHD_WIDGET_CONFIG__;
var M=MODULE_CONFIG.copy;
var S=MODULE_CONFIG.defaults;
var pending=new Map(),nextId=1,rows=[],state={module:DEFAULT_MODULE,config:Object.assign({},S[DEFAULT_MODULE]),lang:'ru',busy:false};
var e={title:document.getElementById('title'),desc:document.getElementById('desc'),search:document.getElementById('search'),refresh:document.getElementById('refresh'),status:document.getElementById('status'),summary:document.getElementById('summary'),tableWrap:document.querySelector('.table-wrap'),visual:document.getElementById('visual'),rows:document.getElementById('rows'),empty:document.getElementById('empty'),details:document.getElementById('details'),detailTitle:document.getElementById('detail-title'),detailSub:document.getElementById('detail-sub'),grid:document.getElementById('grid'),close:document.getElementById('close'),ht:document.getElementById('head-title'),hs:document.getElementById('head-status'),hd:document.getElementById('head-date'),hv:document.getElementById('head-value')};
function lang(){var x=typeof window!=='undefined'&&window.openai&&window.openai.locale;x=String(x||document.documentElement.lang||'ru').toLowerCase();return x.indexOf('en')===0?'en':'ru'}
function t(k){return(C[state.lang]||C.ru)[k]||k}
function obj(x){return x&&typeof x==='object'&&!Array.isArray(x)}
function merge(a,b){return Object.assign({},a||{},b||{})}
function payload(x){if(!x)return null;if(Object.prototype.hasOwnProperty.call(x,'structuredContent'))return x.structuredContent;if(Object.prototype.hasOwnProperty.call(x,'toolOutput'))return x.toolOutput;return x}
function unwrap(x){if(x&&x.isError)throw Error(t('load'));var p=payload(x)||{};if(p.error)throw Error(String(p.error.message||p.error.detail||p.error));if(p.ok===false&&p.message)throw Error(String(p.message));return p}
function applyConfig(p){p=payload(p)||{};var m=obj(p.meta)?p.meta:{},w=obj(m.widget)?m.widget:{},mod=String(w.module||m.module||state.module||DEFAULT_MODULE);state.module=M[mod]?mod:DEFAULT_MODULE;state.config=merge(S[state.module],w);if(!obj(state.config.sourceArgs)&&obj(m.sourceArgs))state.config.sourceArgs=m.sourceArgs;state.lang=lang();labels()}
function labels(){var m=(M[state.module]||M.discussions)[state.lang]||M.discussions.ru;document.documentElement.lang=state.lang;e.title.textContent=m[0];e.desc.textContent=m[1];e.search.placeholder=t('search');e.refresh.textContent=t('refresh');e.empty.textContent=t('empty');e.close.setAttribute('aria-label',t('close'));e.ht.textContent=m[2];e.hs.textContent=t('status');e.hd.textContent=m[3];e.hv.textContent=m[4]}
function req(method,params){var o=typeof window!=='undefined'&&window.openai;if(method==='tools/call'&&o&&typeof o.callTool==='function')return Promise.resolve(o.callTool(params.name,params.arguments||{}));var id=nextId++;window.parent.postMessage({jsonrpc:'2.0',id:id,method:method,params:params||{}},'*');return new Promise(function(resolve,reject){var timeout=window.setTimeout(function(){pending.delete(id);reject(Error(t('load')))},12000);pending.set(id,{resolve:resolve,reject:reject,timeout:timeout})})}
function notify(method,params){window.parent.postMessage({jsonrpc:'2.0',method:method,params:params||{}},'*')}
function path(x,p){return String(p||'').split('.').reduce(function(v,k){return v&&typeof v==='object'?v[k]:undefined},x)}
function findArray(x,d){if(Array.isArray(x))return x;if(!obj(x)||d>2)return null;var keys=['items','records','bookings','upcoming_bookings','topics','notifications','pages','spaces','controllers','agents','user_agents','tables','monitors','data'];for(var i=0;i<keys.length;i++)if(Array.isArray(x[keys[i]]))return x[keys[i]];for(var k in x)if(Object.prototype.hasOwnProperty.call(x,k)){var a=findArray(x[k],d+1);if(a)return a}return null}
function flatten(a){var n=state.config.nested||state.config.nestedCollection;if(!n)return a;var out=[];a.forEach(function(parent){if(parent&&Array.isArray(parent[n]))parent[n].forEach(function(child){var x=Object.assign({},child);if(parent.name&&!x.group_name)x.group_name=parent.name;out.push(x)});else out.push(parent)});return out}
function list(x){var p=payload(x)||{};if(Array.isArray(p.items))return p.items;var ps=Array.isArray(state.config.paths)?state.config.paths:(Array.isArray(state.config.arrayPaths)?state.config.arrayPaths:[]),combined=[];for(var i=0;i<ps.length;i++){var c=path(p,ps[i]),a=Array.isArray(c)?c:findArray(c,0);if(a){if(state.config.combinePaths)combined=combined.concat(a);else return flatten(a)}}if(state.config.combinePaths&&combined.length)return flatten(combined);var d=p.data===undefined?p:p.data;return flatten(Array.isArray(d)?d:(findArray(d,0)||[]))}
function str(x){if(x===null||x===undefined||x==='')return'';if(typeof x==='object'){try{return JSON.stringify(x)}catch(err){return''}}return String(x)}
function id(x,i){var keys=['id','key','code','uuid','page_id','topic_id','booking_id','item_id','monitor_id','table'];for(var n=0;n<keys.length;n++)if(x&&x[keys[n]]!==undefined&&x[keys[n]]!==null&&x[keys[n]]!=='')return str(x[keys[n]]);return String(i+1)}
function status(x){var v=x&&x.status;if(v&&typeof v==='object')v=v.name||v.label||v.code||v.id;return str(v||(x&&(x.state||x.phase||x.health))||'')}
function title(x,i){return str(x&&(x.title||x.name||x.subject||x.display_name||x.label||x.table_name||x.table||x.key))||(t('records')+' '+(i+1))}
function date(x){return str(x&&(x.starts_at||x.start_at||x.due_date||x.checked_at||x.updated_at||x.created_at||x.sent_at||x.published_at||x.date))}
function value(x){if(!x)return'';if(x.availability_percent!==undefined&&x.availability_percent!==null)return str(x.availability_percent)+'%';if(x.quantity!==undefined&&x.quantity!==null)return str(x.quantity);if(x.record_count!==undefined&&x.record_count!==null)return str(x.record_count);return str(x.value!==undefined?x.value:(x.guest_name||x.space_name||x.group_name||x.controller_type||x.type||x.source||x.module||''))}
function sub(x){return str(x&&(x.subtitle||x.project_code||x.space_name||x.channel_name||x.group_name||x.code||x.email))}
function normalize(x,i){var y=obj(x)?Object.assign({},x):{value:x};y.id=id(x,i);y.title=title(x,i);y.subtitle=sub(x);y.status=status(x);y.date=date(x);y.value=value(x);return y}
function dateText(x){if(!x)return'—';var n=Date.parse(x);if(Number.isNaN(n))return String(x);try{return new Intl.DateTimeFormat(state.lang,{dateStyle:'medium',timeStyle:'short'}).format(new Date(n))}catch(err){return String(x)}}
function make(tag,v,cls){var n=document.createElement(tag);if(cls)n.className=cls;if(v!==undefined)n.textContent=v;return n}
function fieldLabel(key){var raw=String(key||'');var labels=FIELD_LABELS[state.lang]||FIELD_LABELS.ru;return labels[raw]||raw.replace(/[_-]+/g,' ')}
function detailText(x,key){if(x===null||x===undefined||x==='')return'—';if(typeof x==='boolean')return x?t('yes'):t('no');if(Array.isArray(x))return'['+x.map(function(v){return detailText(v,key)}).join(', ')+']';if(obj(x))return'{'+Object.keys(x).map(function(k){return fieldLabel(k)+': '+detailText(x[k],k)}).join(', ')+'}';var raw=String(x),labels=VALUE_LABELS[state.lang]||VALUE_LABELS.ru;return labels[raw]||labels[raw.toLowerCase()]||raw}
function renderSummary(){e.summary.replaceChildren();var n=make('span');n.append(make('strong',String(rows.length)),' '+t('records'));e.summary.append(n);var c={};rows.forEach(function(x){if(x.status)c[x.status]=(c[x.status]||0)+1});Object.keys(c).slice(0,3).forEach(function(k){var n=make('span');n.append(make('strong',String(c[k])),' '+detailText(k,'status'));e.summary.append(n)})}
function field(x,keys){for(var i=0;i<keys.length;i++){var v=x&&x[keys[i]];if(v!==undefined&&v!==null&&v!=='')return v}return''}
function numeric(x,keys){var v=field(x,keys);if(v===null||v===undefined||v==='')return null;if(typeof v==='number')return Number.isFinite(v)?v:null;var n=Number(String(v).replace(/,/g,'.').replace(/[^0-9.-]/g,''));return Number.isFinite(n)?n:null}
function groupValue(x,key){var v=key?field(x,[key]):status(x);if(obj(v))v=v.name||v.label||v.code||v.id;return str(v)||'—'}
function visualCard(x,extra){var b=make('button',undefined,'visual-card'+(extra?' '+extra:''));b.type='button';b.append(make('span',x.title||'SHD','visual-title'));if(x.subtitle)b.append(make('span',x.subtitle,'sub'));var line=make('span',undefined,'visual-line');line.append(make('span',detailText(x.status,'status'),'pill'));if(x.date)line.append(make('span',dateText(x.date),'visual-date'));b.append(line);var v=field(x,['total_amount','amount','planned_hours','utilization_percent','score','quantity','revision','pages','value']);if(v!==''&&v!==null)b.append(make('span',detailText(v),'visual-value'));b.onclick=function(){details(x)};return b}
function visualEmpty(){e.visual.replaceChildren(make('div',t('empty'),'empty'))}
function renderKanban(a){if(!a.length){visualEmpty();return}var groups={};a.forEach(function(x){var g=groupValue(x,state.config.groupBy||'status');(groups[g]||(groups[g]=[])).push(x)});var board=make('div',undefined,'kanban');Object.keys(groups).forEach(function(g){var col=make('section',undefined,'kanban-col');var head=make('div',undefined,'kanban-head');head.append(make('span',detailText(g,state.config.groupBy||'status')),make('span',String(groups[g].length),'pill'));col.append(head);var list=make('div',undefined,'kanban-list');groups[g].forEach(function(x){list.append(visualCard(x))});col.append(list);board.append(col)});e.visual.append(board)}
function renderTimeline(a){if(!a.length){visualEmpty();return}var wrap=make('div',undefined,'timeline');a.slice().sort(function(x,y){return String(x.date||'').localeCompare(String(y.date||''))}).forEach(function(x){var item=make('article',undefined,'timeline-item'),rail=make('span',undefined,'timeline-rail'),dot=make('span',undefined,'timeline-dot'),content=make('div',undefined,'timeline-content');content.append(make('div',dateText(x.date),'timeline-meta'),visualCard(x,'timeline-card'));item.append(rail,dot,content);wrap.append(item)});e.visual.append(wrap)}
function renderFunnel(a){if(!a.length){visualEmpty();return}var groups={},amounts={};a.forEach(function(x){var g=groupValue(x,state.config.groupBy||'stage');groups[g]=(groups[g]||0)+1;amounts[g]=(amounts[g]||0)+(numeric(x,['total_amount','amount','value','sum'])||0)});var max=Math.max.apply(null,Object.values(groups));var wrap=make('div',undefined,'funnel');Object.keys(groups).forEach(function(g){var row=make('div',undefined,'funnel-row'),bar=document.createElement('progress');bar.className='funnel-fill';bar.max=max||1;bar.value=groups[g];row.append(make('span',detailText(g,state.config.groupBy||'stage')),bar,make('span',String(groups[g])+(amounts[g]?' · '+String(amounts[g]):'')));wrap.append(row)});e.visual.append(wrap)}
function metric(label,value){var d=make('div',undefined,'metric');d.append(make('span',label,'metric-label'),make('span',value===null||value===undefined?'—':str(value),'metric-value'));return d}
function renderDashboard(a,mode){var grid=make('div',undefined,'metric-grid'),total=a.length,amount=0,planned=0,capacity=0,util=0,overload=0;a.forEach(function(x){amount+=numeric(x,['total_amount','amount','total','sum','value'])||0;planned+=numeric(x,['planned_hours','planned','hours'])||0;capacity+=numeric(x,['capacity_hours','capacity'])||0;util+=numeric(x,['utilization_percent','utilization'])||0;overload+=numeric(x,['overload_hours','overload'])||0});grid.append(metric(t('total'),total));if(mode==='workload'){grid.append(metric(t('planned'),planned||null),metric(t('capacity'),capacity||null),metric(t('utilization'),a.length?Math.round(util/a.length)+'%':null),metric(t('overload'),overload||0))}else{grid.append(metric(t('amount'),amount||null),metric(t('status'),Object.keys(a.reduce(function(m,x){m[groupValue(x,'status')]=1;return m},{})).length))}e.visual.append(grid);if(!a.length){visualEmpty();return}var list=make('div',undefined,'kanban-list');a.slice(0,60).forEach(function(x){list.append(visualCard(x))});e.visual.append(list)}
function approvalValue(x){return groupValue(x,'approval_status')!=='—'?groupValue(x,'approval_status'):groupValue(x,'approval_state')!=='—'?groupValue(x,'approval_state'):groupValue(x,'status')}
function renderStatusSummary(a,keys){if(!a.length){visualEmpty();return}var counts={};a.forEach(function(x){var g=groupValue(x,keys[0]);if(g==='—')g=groupValue(x,keys[1]);if(g==='—')g=groupValue(x,'status');counts[g]=(counts[g]||0)+1});var grid=make('div',undefined,'metric-grid');Object.keys(counts).slice(0,6).forEach(function(g){grid.append(metric(detailText(g,keys[0]),counts[g]))});e.visual.append(grid);var list=make('div',undefined,'kanban-list');a.slice(0,60).forEach(function(x){list.append(visualCard(x))});e.visual.append(list)}
function progressValue(x){var n=numeric(x,['score','completeness_score','completion_percent','progress','availability_percent']);if(n!==null)return Math.max(0,Math.min(100,n));if(x.complete===true||x.is_complete===true||String(status(x)).toLowerCase()==='complete')return 100;return 0}
function renderProgress(a){if(!a.length){visualEmpty();return}var grid=make('div',undefined,'preview-grid');a.forEach(function(x){var card=make('article',undefined,'progress-card');card.append(make('strong',x.title||'SHD','visual-title'));var meta=make('span',x.subtitle||dateText(x.date),'visual-date');card.append(meta);var p=document.createElement('progress');p.className='progress';p.max=100;p.value=progressValue(x);card.append(p,make('span',t('progress')+': '+String(p.value)+'%','visual-value'));card.onclick=function(){details(x)};grid.append(card)});e.visual.append(grid)}
function isWarning(x){var s=String(status(x)).toLowerCase();if(x.low_stock||x.is_low_stock||x.critical||x.warning)return true;if(/low|critical|out|error|overdue|maintenance/.test(s))return true;var q=numeric(x,['quantity','available_quantity','stock']);var min=numeric(x,['min_quantity','minimum_quantity','reorder_level']);return q!==null&&min!==null&&q<=min}
function renderWarnings(a){var warnings=a.filter(isWarning),grid=make('div',undefined,'metric-grid');grid.append(metric(t('warnings'),warnings.length),metric(t('total'),a.length));e.visual.append(grid);if(!warnings.length){e.visual.append(make('div',t('noWarnings'),'empty'));return}var list=make('div',undefined,'kanban-list');warnings.forEach(function(x){list.append(visualCard(x,'warning'))});e.visual.append(list)}
function renderCalendar(a){if(!a.length){visualEmpty();return}var days={};a.forEach(function(x){var d=x.date?String(x.date).slice(0,10):'—';(days[d]||(days[d]=[])).push(x)});var cal=make('div',undefined,'calendar');Object.keys(days).forEach(function(d){var day=make('section',undefined,'calendar-day');day.append(make('div',d,'calendar-head'));days[d].forEach(function(x){day.append(visualCard(x))});cal.append(day)});e.visual.append(cal)}
function memberName(x){var v=field(x,['member_name','username','email','name','display_name','user']);if(obj(v))return str(v.name||v.username||v.email||v.id);return str(v)||x.id}
function permissionText(x){var p=x.permissions||x.permission||x.scopes||'';if(obj(p))return Object.keys(p).filter(function(k){return p[k]===true||p[k]===1||typeof p[k]==='string'}).map(function(k){return fieldLabel(k)+(typeof p[k]==='string'?'='+detailText(p[k],k):'')}).join(', ')||'—';if(Array.isArray(p))return p.map(function(k){return detailText(k,'permissions')}).join(', ')||'—';return detailText(p,'permissions')||'—'}
function renderMatrix(a){if(!a.length){visualEmpty();return}var wrap=make('div',undefined,'matrix-wrap'),table=make('table',undefined,'matrix-table'),head=make('tr');[t('member'),t('role'),t('status'),t('permissions')].forEach(function(x){head.append(make('th',x))});table.append(make('thead').appendChild(head).parentNode);var body=make('tbody');a.forEach(function(x){var row=make('tr');row.append(make('td',memberName(x)),make('td',detailText(field(x,['role','organization_role','type']),'role')||'—'),make('td',detailText(status(x),'status')||'—'),make('td',permissionText(x)));row.onclick=function(){details(x)};row.className='record';body.append(row)});table.append(body);wrap.append(table);e.visual.append(wrap)}
function treeId(x,i){return str(field(x,['id','space_id','page_id','collection_id','key']))||String(i)}
function treeParent(x){return str(field(x,['parent_id','parentId','parent_space_id','parent_collection_id']))}
function renderTree(a){if(!a.length){visualEmpty();return}var by={};a.forEach(function(x,i){var p=treeParent(x);(by[p]||(by[p]=[])).push({x:x,i:i})});var tree=make('div',undefined,'tree');function branch(parent,host,seen){(by[parent]||[]).forEach(function(entry){var id=treeId(entry.x,entry.i);if(seen[id])return;var wrap=make('div',undefined,'tree-node');wrap.append(visualCard(entry.x));host.append(wrap);var child=make('div',undefined,'tree-children');branch(id,child,Object.assign({},seen,(function(){var o={};o[id]=true;return o})()));if(child.childNodes.length)wrap.append(child)})}branch('',tree,{});if(!tree.childNodes.length)a.forEach(function(x){tree.append(visualCard(x))});e.visual.append(tree)}
function renderPreview(a){if(!a.length){visualEmpty();return}var grid=make('div',undefined,'preview-grid');a.forEach(function(x){var card=make('article',undefined,'preview-card');card.append(make('strong',x.title||'SHD','visual-title'));var fields=make('div',undefined,'preview-fields');Object.keys(x).filter(function(k){return['id','title','subtitle','status','date','value','meta'].indexOf(k)===-1}).slice(0,10).forEach(function(k){var cell=make('div',undefined,'preview-field');cell.append(make('strong',fieldLabel(k)+': '),make('span',detailText(x[k],k)));fields.append(cell)});card.append(fields);card.onclick=function(){details(x)};grid.append(card)});e.visual.append(grid)}
function renderVisual(a){e.visual.replaceChildren();var v=state.config.view||'table';if(v==='kanban')renderKanban(a);else if(v==='workload'||v==='dashboard')renderDashboard(a,v);else if(v==='timeline')renderTimeline(a);else if(v==='funnel')renderFunnel(a);else if(v==='approval')renderStatusSummary(a,['approval_status','approval_state']);else if(v==='completeness'||v==='progress')renderProgress(a);else if(v==='health')renderStatusSummary(a,['health','status']);else if(v==='calendar')renderCalendar(a);else if(v==='matrix')renderMatrix(a);else if(v==='warnings')renderWarnings(a);else if(v==='tree')renderTree(a);else if(v==='preview')renderPreview(a);else renderDashboard(a,'dashboard')}
function renderTable(a){e.rows.replaceChildren();e.empty.hidden=a.length!==0;a.forEach(function(x){var r=document.createElement('tr');r.className='record';r.tabIndex=0;r.setAttribute('role','button');r.setAttribute('aria-label',x.title);var n=make('td');n.append(make('span',x.title,'title'));if(x.subtitle)n.append(make('span',x.subtitle,'sub'));var s=make('span',detailText(x.status,'status'),'pill');if(x.status)s.classList.add(String(x.status).toLowerCase().replace(/[^a-z0-9_-]+/g,'-'));var sc=make('td');sc.append(s);r.append(n,sc,make('td',dateText(x.date)),make('td',detailText(x.value,'value')));r.onclick=function(){details(x)};r.onkeydown=function(ev){if(ev.key==='Enter'||ev.key===' '){ev.preventDefault();details(x)}};e.rows.append(r)});renderSummary()}
function renderRows(){var q=e.search.value.trim().toLowerCase(),a=rows.filter(function(x){return!q||[x.title,x.subtitle,x.status,x.id,x.value].map(str).join(' ').toLowerCase().indexOf(q)!==-1});if(state.config.view&&state.config.view!=='table'){e.tableWrap.hidden=true;e.visual.hidden=false;e.empty.hidden=true;renderVisual(a);renderSummary();return}e.tableWrap.hidden=false;e.visual.hidden=true;renderTable(a)}
function render(x){var p=unwrap(x);applyConfig(p);rows=list(p).map(normalize);var dir=state.config.sort==='asc'?1:-1;rows.sort(function(a,b){var x=Date.parse(a.date),y=Date.parse(b.date);if(!Number.isNaN(x)&&!Number.isNaN(y)&&x!==y)return(x-y)*dir;return a.title.localeCompare(b.title,state.lang)*dir});renderRows()}
function setStatus(x,bad){e.status.textContent=x||'';e.status.className=bad?'status error':'status'}
function add(k,v,key){var d=make('div',undefined,'detail');d.append(make('span',k,'label'),make('span',detailText(v,key),'value'));e.grid.append(d)}
function show(x){e.detailTitle.textContent=x.title||'SHD';e.detailSub.textContent=x.subtitle||x.id||'';e.grid.replaceChildren();add(t('id'),x.id,'id');if(x.status)add(t('status'),x.status,'status');if(x.date)add(t('date'),dateText(x.date),'date');if(x.value)add(t('value'),x.value,'value');Object.keys(x).filter(function(k){return['id','title','subtitle','status','date','value','meta'].indexOf(k)===-1}).slice(0,8).forEach(function(k){add(fieldLabel(k),x[k],k)});e.details.hidden=false}
function details(x){show(x);if(!state.config.readTool)return;var k=state.config.detailKey;if(!k)return;var v=x[k]===undefined?x.id:x[k];if(v===undefined||v===null||v==='')return;if(state.module==='status-page'&&!/^\d+$/.test(String(v)))return;var a={};if(Array.isArray(state.config.detailSourceArgs)){var ctx=merge(state.config.listArgs,state.config.sourceArgs);state.config.detailSourceArgs.forEach(function(name){if(ctx[name]!==undefined)a[name]=ctx[name]})}else a=merge(state.config.sourceArgs,state.config.detailArgs);a=merge(a,state.config.detailArgs);a[k]=k==='table'?String(v):(Number(v)||v);req('tools/call',{name:state.config.readTool,arguments:a}).then(function(result){var p=unwrap(result),d=p.data===undefined?p:p.data;e.grid.replaceChildren();if(Array.isArray(d)){add(t('records'),d.length);d.slice(0,6).forEach(function(q,i){add(String(i+1),q&&(q.message||q.body||q.content||q.title||q))})}else if(obj(d))Object.keys(d).slice(0,12).forEach(function(q){add(fieldLabel(q),d[q],q)});else add(t('details'),d)}).catch(function(err){setStatus(err.message||t('detail'),true)})}
function contextReady(a){if(!state.config.contextRequired)return true;if(Array.isArray(state.config.contextSets)&&state.config.contextSets.length)return state.config.contextSets.every(function(set){return set.some(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''})});var keys=Array.isArray(state.config.contextKeys)&&state.config.contextKeys.length?state.config.contextKeys:['base','project_code','project'];var ready=state.config.contextAll?keys.every(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''}):keys.some(function(k){return a[k]!==undefined&&a[k]!==null&&a[k]!==''});return ready}
function refresh(){if(state.busy)return;var a=merge(state.config.listArgs,state.config.sourceArgs);if(!contextReady(a)){setStatus(t('context'),true);return}if(!state.config.listTool)return;state.busy=true;e.refresh.disabled=true;setStatus(t('loading'),false);req('tools/call',{name:state.config.listTool,arguments:a}).then(function(x){render(x);setStatus('',false)}).catch(function(err){setStatus(err.message||t('load'),true)}).finally(function(){state.busy=false;e.refresh.disabled=false})}
function init(){return req('ui/initialize',{protocolVersion:'2025-11-25',appInfo:{name:'SHD '+(M[state.module]||M.discussions).ru[0],title:'SHD MCP',version:'1.0.0',websiteUrl:'https://shd.xyz.su'},appCapabilities:{}}).then(function(){notify('ui/notifications/initialized',{})}).catch(function(){})}
window.addEventListener('message',function(ev){if(ev.source!==window.parent)return;var m=ev.data;if(!m||m.jsonrpc!=='2.0')return;if(m.id!==undefined&&pending.has(m.id)){var q=pending.get(m.id);pending.delete(m.id);window.clearTimeout(q.timeout);if(m.error)q.reject(Error(m.error.message||t('load')));else q.resolve(m.result);return}if(m.method==='ui/notifications/tool-input'&&!rows.length){try{render(m.params||{})}catch(err){setStatus(err.message||t('load'),true)}}if(m.method==='ui/notifications/tool-result'){try{render(m.params&&m.params.structuredContent)}catch(err){setStatus(err.message||t('load'),true)}}},{passive:true});
e.search.oninput=renderRows;e.refresh.onclick=refresh;e.close.onclick=function(){e.details.hidden=true};state.lang=lang();labels();void init();var out=typeof window!=='undefined'&&window.openai?window.openai.toolOutput:null;if(out){try{render(out)}catch(err){setStatus(err.message||t('load'),true)}}else{var input=typeof window!=='undefined'&&window.openai?window.openai.toolInput:null;if(input){try{render(input)}catch(err){setStatus(err.message||t('load'),true)}}else refresh()}
}());
</script>
<script data-shd-widget-write-actions="js">
(function () {
'use strict';
if (window.__SHD_WIDGET_WRITE_ACTIONS__) return;
window.__SHD_WIDGET_WRITE_ACTIONS__ = true;
var copy = {
ru: {
actions: 'Действия',
chooseAction: 'Выберите действие',
selectRecord: 'Сначала выберите запись.',
submit: 'Сохранить',
cancel: 'Отмена',
confirm: 'Подтверждаю действие',
warning: 'Проверьте данные перед записью.',
destructive: 'Операция может изменить или удалить данные.',
required: 'Заполните обязательное поле.',
invalidJson: 'Введите корректный JSON.',
success: 'Изменения сохранены.',
failure: 'Не удалось сохранить изменения.',
loading: 'Сохраняю…',
noActions: 'Для этого виджета нет доступных действий.',
refreshed: 'Список обновлён.',
fieldValue: 'Значение',
},
en: {
actions: 'Actions',
chooseAction: 'Choose an action',
selectRecord: 'Select a record first.',
submit: 'Save',
cancel: 'Cancel',
confirm: 'I confirm this action',
warning: 'Review the values before writing.',
destructive: 'This operation may change or remove data.',
required: 'Fill in the required field.',
invalidJson: 'Enter valid JSON.',
success: 'Changes saved.',
failure: 'Could not save changes.',
loading: 'Saving…',
noActions: 'No actions are available for this widget.',
refreshed: 'List refreshed.',
fieldValue: 'Value',
},
};
var state = {
widget: {},
records: [],
selected: null,
action: null,
inputs: {},
busy: false,
lang: String(document.documentElement.lang || 'ru').toLowerCase().indexOf('ru') === 0 ? 'ru' : 'en',
};
var pending = new Map();
var nextRequestId = 1;
var elements = {};
function t(key) {
return (copy[state.lang] && copy[state.lang][key]) || copy.en[key] || key;
}
function object(value) {
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
}
function normalize(value) {
if (!value) return null;
if (value.structuredContent) return normalize(value.structuredContent);
if (value.toolOutput) return normalize(value.toolOutput);
if (value.toolInput) return normalize(value.toolInput);
if (value.arguments && !value.data && !value.items && !value.projects) return normalize(value.arguments);
return value;
}
function widgetFrom(value) {
var payload = normalize(value) || {};
var meta = object(payload.meta) ? payload.meta : {};
return object(meta.widget) ? meta.widget : {};
}
function recordsFrom(value) {
var payload = normalize(value) || {};
var data = Array.isArray(payload.items) ? payload.items : payload.projects;
if (!Array.isArray(data)) data = payload.data;
if (data && !Array.isArray(data) && Array.isArray(data.data)) data = data.data;
return Array.isArray(data) ? data.filter(function (item) { return object(item); }) : [];
}
function readPath(value, path) {
if (!value || path === undefined || path === null) return undefined;
var parts = String(path).split('.');
var current = value;
for (var index = 0; index < parts.length; index += 1) {
if (current === undefined || current === null) return undefined;
current = current[parts[index]];
}
return current;
}
function firstValue(paths) {
var list = Array.isArray(paths) ? paths : [paths];
var sources = [state.selected, state.widget.sourceArgs, state.widget.listArgs];
for (var sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
var sourceObject = sources[sourceIndex];
if (!sourceObject) continue;
for (var pathIndex = 0; pathIndex < list.length; pathIndex += 1) {
var value = readPath(sourceObject, list[pathIndex]);
if (value !== undefined && value !== null && value !== '') return value;
}
}
return undefined;
}
function text(value) {
if (value === undefined || value === null) return '';
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
}
function label(value) {
if (object(value)) return value[state.lang] || value.ru || value.en || t('fieldValue');
return String(value || t('fieldValue'));
}
function actionLabel(action) {
return label(action && action.label) || action.id || action.tool || t('actions');
}
function hasTargets(action) {
return Boolean(action && ((Array.isArray(action.targets) && action.targets.length) || action.target));
}
function actionList() {
return Array.isArray(state.widget.actions) ? state.widget.actions.filter(function (action) {
return action && action.tool;
}) : [];
}
function request(method, params) {
var openai = typeof window !== 'undefined' ? window.openai : undefined;
if (method === 'tools/call' && openai && typeof openai.callTool === 'function') {
return openai.callTool(params.name, params.arguments || {});
}
var id = nextRequestId++;
window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params || {} }, '*');
return new Promise(function (resolve, reject) {
var timeout = window.setTimeout(function () {
pending.delete(id);
reject(new Error(t('failure')));
}, 15000);
pending.set(id, { resolve: resolve, reject: reject, timeout: timeout });
});
}
function unwrap(value) {
var payload = normalize(value);
if (value && value.isError) throw new Error(payload && payload.error ? text(payload.error) : t('failure'));
if (payload && payload.error) throw new Error(text(payload.error));
return payload || value;
}
function make(tag, value, className) {
var node = document.createElement(tag);
if (className) node.className = className;
if (value !== undefined) node.textContent = value;
return node;
}
function ensurePanel() {
if (elements.panel) return;
var root = document.querySelector('main') || document.body;
var panel = make('section', undefined, 'shd-widget-actions');
panel.hidden = true;
panel.setAttribute('aria-live', 'polite');
var header = make('div', undefined, 'shd-widget-actions-header');
header.append(make('strong', t('actions')), make('span', '', 'shd-widget-actions-selection'));
var select = document.createElement('select');
select.className = 'shd-widget-actions-select';
select.setAttribute('aria-label', t('actions'));
var form = make('form', undefined, 'shd-widget-actions-form');
var status = make('div', '', 'shd-widget-actions-status');
var buttons = make('div', undefined, 'shd-widget-actions-buttons');
var submit = make('button', t('submit'), 'shd-widget-actions-submit');
submit.type = 'submit';
var cancel = make('button', t('cancel'), 'shd-widget-actions-cancel');
cancel.type = 'button';
buttons.append(submit, cancel);
panel.append(header, select, form, status, buttons);
root.append(panel);
elements = { panel: panel, selection: header.lastChild, select: select, form: form, status: status, submit: submit, cancel: cancel };
select.addEventListener('change', function () {
var selected = actionList().find(function (action) { return action.id === select.value; });
state.action = selected || null;
renderForm();
});
cancel.addEventListener('click', function () {
state.action = null;
select.value = '';
renderForm();
});
form.addEventListener('submit', function (event) {
event.preventDefault();
submitAction();
});
}
function inputValue(fieldConfig) {
var value = firstValue(fieldConfig.source || []);
if (value === undefined && fieldConfig.default !== undefined) value = fieldConfig.default;
return value;
}
function createInput(fieldConfig) {
var type = fieldConfig.type || 'text';
var input;
if (type === 'textarea' || type === 'json') {
input = document.createElement('textarea');
input.rows = type === 'json' ? 4 : 3;
if (type === 'json') input.placeholder = '{ }';
} else if (type === 'select') {
input = document.createElement('select');
(fieldConfig.options || []).forEach(function (option) {
var item = document.createElement('option');
item.value = String(option[0]);
item.textContent = state.lang === 'ru' ? String(option[1]) : String(option[2] || option[1]);
input.append(item);
});
} else {
input = document.createElement('input');
input.type = type === 'number' || type === 'email' ? type : 'text';
}
input.className = 'shd-widget-actions-input';
input.name = fieldConfig.name;
var value = inputValue(fieldConfig);
if (type === 'checkbox') {
input.type = 'checkbox';
input.checked = value === true || value === 'true' || value === 1 || value === '1';
} else if (value !== undefined && value !== null) {
input.value = type === 'json' && typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value);
}
if (fieldConfig.required) input.required = true;
return input;
}
function renderForm() {
ensurePanel();
var selectedActionId = state.action ? state.action.id : '';
elements.select.replaceChildren();
var placeholder = document.createElement('option');
placeholder.value = '';
placeholder.textContent = t('chooseAction');
elements.select.append(placeholder);
actionList().forEach(function (action) {
var option = document.createElement('option');
option.value = action.id;
option.textContent = actionLabel(action);
elements.select.append(option);
});
elements.select.value = selectedActionId;
elements.select.hidden = actionList().length === 0;
elements.form.replaceChildren();
elements.status.textContent = '';
elements.submit.disabled = state.busy;
var action = state.action;
if (!action) {
elements.selection.textContent = state.selected ? text(state.selected.title || state.selected.name || state.selected.id || '') : '';
elements.submit.hidden = true;
elements.cancel.hidden = true;
if (actionList().length && !state.selected && actionList().every(hasTargets)) {
elements.form.append(make('div', t('selectRecord'), 'shd-widget-actions-hint'));
}
return;
}
elements.selection.textContent = state.selected ? text(state.selected.title || state.selected.name || state.selected.id || '') : '';
elements.submit.hidden = false;
elements.cancel.hidden = false;
if (hasTargets(action) && !state.selected) elements.form.append(make('div', t('selectRecord'), 'shd-widget-actions-hint'));
(action.fields || []).forEach(function (fieldConfig) {
if (!fieldConfig || !fieldConfig.name || fieldConfig.generated || fieldConfig.type === 'hidden') return;
var wrapper = make('label', undefined, 'shd-widget-actions-field');
wrapper.append(make('span', label(fieldConfig.label)));
wrapper.append(createInput(fieldConfig));
elements.form.append(wrapper);
});
if (action.confirm) {
var confirmLabel = make('label', undefined, 'shd-widget-actions-confirm');
var confirm = document.createElement('input');
confirm.type = 'checkbox';
confirm.name = '__confirm';
confirm.required = true;
confirmLabel.append(confirm, make('span', t('confirm')));
elements.form.append(confirmLabel);
}
var notice = make('div', action.destructive ? t('destructive') : t('warning'), action.destructive ? 'shd-widget-actions-warning shd-widget-actions-danger' : 'shd-widget-actions-warning');
elements.form.append(notice);
}
function coerce(value, type) {
if (type === 'number') {
var number = Number(value);
return Number.isFinite(number) ? number : value;
}
if (type === 'checkbox') return Boolean(value);
if (type === 'json') {
try { return JSON.parse(value); } catch (error) { throw new Error(t('invalidJson')); }
}
return value;
}
function targetType(name) {
return /^(task|document|contract|booking|item|monitor|proposal|approval|page|notification|topic|membership|organization|space|status|project)_?id$/.test(name) || name === 'issue_number' || name === 'number' ? 'number' : 'text';
}
function targetList(action) {
if (Array.isArray(action.targets)) return action.targets;
return action.target ? [action.target] : [];
}
function collectArguments(action) {
var args = Object.assign({}, action.fixed || {});
(action.context || []).forEach(function (name) {
var value = firstValue([name]);
if (value !== undefined && value !== null && value !== '') args[name] = value;
});
targetList(action).forEach(function (targetConfig) {
var value = firstValue(targetConfig.source || targetConfig.name);
if (value === undefined || value === null || value === '') throw new Error(t('selectRecord'));
args[targetConfig.name] = coerce(value, targetConfig.type || targetType(targetConfig.name));
});
(action.fields || []).forEach(function (fieldConfig) {
if (!fieldConfig || !fieldConfig.name || fieldConfig.generated || fieldConfig.type === 'hidden') return;
var input = Array.prototype.find.call(elements.form.querySelectorAll('[name]'), function (node) {
return node.getAttribute('name') === fieldConfig.name;
});
if (!input) return;
var raw = fieldConfig.type === 'checkbox' ? input.checked : input.value.trim();
if (raw === '' && !input.checked && !fieldConfig.required) return;
if (raw === '' && fieldConfig.required) throw new Error(t('required'));
args[fieldConfig.name] = coerce(raw, fieldConfig.type || 'text');
});
if (action.idempotencyKey) args.idempotency_key = 'shd-widget-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 10);
if (action.confirmArg) args.confirm = true;
return args;
}
function setStatus(message, error) {
elements.status.textContent = message || '';
elements.status.className = 'shd-widget-actions-status' + (error ? ' shd-widget-actions-status-error' : '');
}
function submitAction() {
if (state.busy || !state.action) return;
var action = state.action;
var args;
try { args = collectArguments(action); } catch (error) { setStatus(error.message || t('failure'), true); return; }
state.busy = true;
elements.submit.disabled = true;
elements.cancel.disabled = true;
setStatus(t('loading'), false);
request('tools/call', { name: action.tool, arguments: args }).then(function (result) {
unwrap(result);
setStatus(t('success'), false);
var refresh = document.getElementById('refresh');
if (refresh && typeof refresh.click === 'function') window.setTimeout(function () { refresh.click(); }, 250);
}).catch(function (error) {
setStatus(error && error.message ? error.message : t('failure'), true);
}).finally(function () {
state.busy = false;
elements.submit.disabled = false;
elements.cancel.disabled = false;
});
}
function recordFromNode(node) {
var content = String(node.textContent || '').toLowerCase();
var exact = state.records.find(function (item) {
var id = text(item.id || item.code || item.key || item.number).toLowerCase();
return id && content.indexOf(id) !== -1;
});
if (exact) return exact;
return state.records.find(function (item) {
var title = text(item.title || item.name || item.display_name || item.number).toLowerCase();
return title && content.indexOf(title) !== -1;
}) || null;
}
function update(value) {
var payload = normalize(value) || {};
var widget = widgetFrom(payload);
if (Object.keys(widget).length) state.widget = widget;
var records = recordsFrom(payload);
if (records.length || Array.isArray(payload.data) || Array.isArray(payload.items) || Array.isArray(payload.projects)) state.records = records;
ensurePanel();
var actions = actionList();
elements.panel.hidden = actions.length === 0;
if (actions.length && !state.action) renderForm();
else if (actions.length) renderForm();
}
document.addEventListener('click', function (event) {
var node = event.target && event.target.closest ? event.target.closest('.record,.project-row,.visual-card,.progress-card,.preview-card,.calendar-card,.timeline-card,.kanban-card') : null;
if (!node || (elements.panel && elements.panel.contains(node))) return;
var item = recordFromNode(node);
if (!item) return;
state.selected = item;
ensurePanel();
renderForm();
}, true);
window.addEventListener('message', function (event) {
if (event.source !== window.parent) return;
var message = event.data;
if (!message || message.jsonrpc !== '2.0') return;
if (message.id !== undefined && pending.has(message.id)) {
var requestState = pending.get(message.id);
pending.delete(message.id);
window.clearTimeout(requestState.timeout);
if (message.error) requestState.reject(new Error(message.error.message || t('failure')));
else requestState.resolve(message.result);
return;
}
if (message.method === 'ui/notifications/tool-input' || message.method === 'ui/notifications/tool-result') update(message.params || {});
}, { passive: true });
ensurePanel();
var initial = typeof window !== 'undefined' && window.openai ? (window.openai.toolOutput || window.openai.toolInput) : null;
if (initial) update(initial);
}());
</script>
</body>
</html>
@@ -1 +1 @@
9e82d5a6f3c35445b5d7400fd4b1aa8ba93c97141cf83a23e585ef051e3f0421 index.html e0595452b8365206b4bb26cf5b2680c8da2e7107260a0caf7bb0c5c23008743b index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/acl-matrix/v1.html. This artifact is served by the SHD MCP backend as ui://shd/acl-matrix/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only organization membership and permission matrix. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77269, "bytes": 78910,
"gzipBytes": 20605, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/acl-matrix/v1.html", "uri": "ui://shd/acl-matrix/v1.html",
"sha256": "9e82d5a6f3c35445b5d7400fd4b1aa8ba93c97141cf83a23e585ef051e3f0421" "sha256": "e0595452b8365206b4bb26cf5b2680c8da2e7107260a0caf7bb0c5c23008743b"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/acl-matrix/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
463ba23dc7332ba8a2e5340bb19d3951d798c7ecc700209fc46f659cfdbe253c index.html 762673741389a380d738687823f4badf206d4c248ea3790f0d94f7921931c6d1 index.html
@@ -265,7 +265,7 @@
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var timeout = window.setTimeout(function () { var timeout = window.setTimeout(function () {
pendingRequests.delete(id); pendingRequests.delete(id);
reject(new Error('Host request timed out')); reject(new Error(t.refreshError));
}, 10000); }, 10000);
pendingRequests.set(id, { resolve: resolve, reject: reject, timeout: timeout }); pendingRequests.set(id, { resolve: resolve, reject: reject, timeout: timeout });
}); });
@@ -5,11 +5,11 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 45447, "bytes": 45437,
"gzipBytes": 11470, "gzipBytes": 11305,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/active-projects/v1.html", "uri": "ui://shd/active-projects/v1.html",
"sha256": "463ba23dc7332ba8a2e5340bb19d3951d798c7ecc700209fc46f659cfdbe253c" "sha256": "762673741389a380d738687823f4badf206d4c248ea3790f0d94f7921931c6d1"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
@@ -1 +1 @@
fb24651b2efe2b66c490b839d9c7c3c679c77c1f58f00d9bcfce199c522761ca index.html 870534faae707f202e8fb1d21fdbeee0fc1432236698ae277b5dd99b1f331b51 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/activity/v1.html. This artifact is served by the SHD MCP backend as ui://shd/activity/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides Authenticated realtime activity stream. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 53670, "bytes": 78906,
"gzipBytes": 15487, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/activity/v1.html", "uri": "ui://shd/activity/v1.html",
"sha256": "fb24651b2efe2b66c490b839d9c7c3c679c77c1f58f00d9bcfce199c522761ca" "sha256": "870534faae707f202e8fb1d21fdbeee0fc1432236698ae277b5dd99b1f331b51"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/activity/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
866750c8b84f0879d50035ce6fb4b58174a33c3dafdfa7ed72a9ef747c6d4146 index.html dac1b47d9e6008708b464f7e310125ed1709e07955bd0c6adc5e9595c6bb5a09 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/agents-health/v1.html. This artifact is served by the SHD MCP backend as ui://shd/agents-health/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only agent health overview; controller topology and telemetry health are rendered by the controller-observability widget. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77403, "bytes": 78916,
"gzipBytes": 20585, "gzipBytes": 21188,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/agents-health/v1.html", "uri": "ui://shd/agents-health/v1.html",
"sha256": "866750c8b84f0879d50035ce6fb4b58174a33c3dafdfa7ed72a9ef747c6d4146" "sha256": "dac1b47d9e6008708b464f7e310125ed1709e07955bd0c6adc5e9595c6bb5a09"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/agents-health/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
b556fb2c97029bf075b97613dbd1e90cda5576c0f13be463c2becd3c0db2a0e6 index.html 260762f0d2f01562d087ea0d6cf7a84c374ee047fd56527fd89612472d065ce2 index.html
@@ -1,5 +1,5 @@
# Agents widget # Agents widget
This artifact is served by the SHD MCP backend as `ui://shd/agents/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/agents/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The widget provides agent entities with read-only status context; project controller topology is rendered by the separate controller-observability widget. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 49008, "bytes": 78902,
"gzipBytes": 14343, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/agents/v1.html", "uri": "ui://shd/agents/v1.html",
"sha256": "b556fb2c97029bf075b97613dbd1e90cda5576c0f13be463c2becd3c0db2a0e6" "sha256": "260762f0d2f01562d087ea0d6cf7a84c374ee047fd56527fd89612472d065ce2"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/agents/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
c2fcb36329b0588f621577863f6b7549bf622732fd88d4bcead254bd68b6642c index.html fca8fbc1630bcb60d699442903900cec2eee02e863bb5d8523c6317b4d2d1b53 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/airbase-preview/v1.html. This artifact is served by the SHD MCP backend as ui://shd/airbase-preview/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only AirBase table record preview. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 76306, "bytes": 78920,
"gzipBytes": 20462, "gzipBytes": 21188,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/airbase-preview/v1.html", "uri": "ui://shd/airbase-preview/v1.html",
"sha256": "c2fcb36329b0588f621577863f6b7549bf622732fd88d4bcead254bd68b6642c" "sha256": "fca8fbc1630bcb60d699442903900cec2eee02e863bb5d8523c6317b4d2d1b53"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/airbase-preview/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
4f331c4d7e295f080dfdbcd9360f860696817e0ccd2ffc0ca92f2790330faefb index.html ceb3f0a4bca8c7dfa85ef5fbe705f0767523be2b10637bc1e3f767edab7ccb49 index.html
@@ -1,5 +1,5 @@
# AirBase widget # AirBase widget
This artifact is served by the SHD MCP backend as `ui://shd/airbase/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/airbase/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The widget provides table catalog and read-only schema details. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 48201, "bytes": 78904,
"gzipBytes": 14230, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/airbase/v1.html", "uri": "ui://shd/airbase/v1.html",
"sha256": "4f331c4d7e295f080dfdbcd9360f860696817e0ccd2ffc0ca92f2790330faefb" "sha256": "ceb3f0a4bca8c7dfa85ef5fbe705f0767523be2b10637bc1e3f767edab7ccb49"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/airbase/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
de6f1c6e07d74bc58555f41a22e9a0e8a3942577ffce5773f9450707cb7ca440 index.html 74cc8cd974ddfb0563b213166398cf37e98cd477e010c9fbcc444324b5366a5c index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as `ui://shd/controller-observability/v1.html`. This artifact is served by the SHD MCP backend as `ui://shd/controller-observability/v1.html`.
It is a standalone parameterized MCP App. The same UI contract renders controller history, series comparison, expert settings, lightweight topology, telemetry health and source audit modes; source inventory is loaded separately through the bounded paginated inventory request. It is a standalone parameterized MCP App. The same UI contract renders controller device inventory, live state, server-computed state statistics, history, series comparison, expert settings, lightweight topology, telemetry health and source audit modes. A selected device profile is loaded on demand through the profile tool.
@@ -6,7 +6,7 @@
<title>SHD controller observability</title> <title>SHD controller observability</title>
<style> <style>
:root{color-scheme:light dark;--bg:#f7f9fb;--panel:#edf1f5;--line:rgba(31,41,55,.16);--ink:#20242a;--muted:#69717c;--accent:#4f5967;--soft:rgba(79,89,103,.12);--good:#2f855a;--bad:#b42318;--button:#fff;--shadow:0 10px 26px rgba(31,41,55,.1)} :root{color-scheme:light dark;--bg:#f7f9fb;--panel:#edf1f5;--line:rgba(31,41,55,.16);--ink:#20242a;--muted:#69717c;--accent:#4f5967;--soft:rgba(79,89,103,.12);--good:#2f855a;--bad:#b42318;--button:#fff;--shadow:0 10px 26px rgba(31,41,55,.1)}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.shell{max-width:1100px;margin:auto;padding:18px}.card{padding:18px;border:1px solid var(--line);border-radius:17px;background:var(--panel);box-shadow:var(--shadow)}.eyebrow{color:var(--muted);font-size:11px;font-weight:750;letter-spacing:.12em;text-transform:uppercase}h1{margin:4px 0 4px;font-size:24px;line-height:1.15}.lede{margin:0;color:var(--muted)}.toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:16px 0 10px}.toolbar button,.toolbar select{border:1px solid var(--line);border-radius:9px;padding:9px 11px;background:var(--bg);color:inherit}.toolbar button{cursor:pointer;font-weight:650}.toolbar .primary{border-color:var(--accent);background:var(--accent);color:var(--button)}.toolbar button:disabled{opacity:.58;cursor:wait}.toolbar select{min-width:150px}.status{min-height:22px;margin:4px 0;color:var(--muted)}.error{color:var(--bad)}.summary{display:flex;gap:7px;flex-wrap:wrap;margin:12px 0}.pill{display:inline-block;padding:4px 8px;border-radius:999px;background:var(--soft);color:var(--accent);font-size:12px}.pill.good{color:var(--good);background:rgba(47,133,90,.14)}.pill.bad{color:var(--bad);background:rgba(180,35,24,.12)}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px}.panel{padding:13px;border:1px solid var(--line);border-radius:12px;background:var(--bg)}.panel h2,.panel h3{margin:0 0 8px;font-size:16px}.panel p{margin:4px 0;color:var(--muted)}.chart{min-height:128px;margin:8px 0;padding:7px;border:1px solid var(--line);border-radius:10px;background:var(--bg);overflow:hidden}.chart svg{display:block;width:100%;height:115px}.chart-empty{display:grid;place-items:center;height:112px;color:var(--muted);font-size:12px}.axis{fill:var(--muted);font-size:10px}.line-a{fill:none;stroke:#4f7cac;stroke-width:2.2}.line-b{fill:none;stroke:#b56a3c;stroke-width:2.2}.table-wrap{max-height:460px;overflow:auto;border:1px solid var(--line);border-radius:11px;background:var(--bg)}table{width:100%;border-collapse:collapse;min-width:600px}th,td{padding:9px 11px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}th{position:sticky;top:0;background:var(--panel);color:var(--muted);font-size:12px;z-index:1}tr:last-child td{border-bottom:0}.muted{color:var(--muted)}.empty{padding:24px;text-align:center;color:var(--muted)}.source-list{display:grid;gap:7px}.source-row{display:flex;justify-content:space-between;gap:10px;align-items:center;padding:8px 10px;border-radius:9px;background:var(--soft)}.setting{display:grid;grid-template-columns:minmax(130px,1fr) minmax(150px,1fr);gap:9px;align-items:center;padding:9px 0;border-bottom:1px solid var(--line)}.setting:last-child{border-bottom:0}.setting label{font-weight:650}.setting small{display:block;color:var(--muted);font-weight:400}.setting input,.setting select{width:100%;border:1px solid var(--line);border-radius:8px;padding:8px;background:var(--bg);color:inherit}.setting input:disabled,.setting select:disabled{opacity:.6}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}.actions button{border:1px solid var(--accent);border-radius:8px;padding:8px 12px;background:var(--accent);color:var(--button);cursor:pointer}.actions button:disabled{opacity:.6;cursor:wait}@media(max-width:650px){.shell{padding:10px}.card{padding:14px}.setting{grid-template-columns:1fr}.table-wrap{margin:0 -2px}} *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}.shell{max-width:1100px;margin:auto;padding:18px}.card{padding:18px;border:1px solid var(--line);border-radius:17px;background:var(--panel);box-shadow:var(--shadow)}.eyebrow{color:var(--muted);font-size:11px;font-weight:750;letter-spacing:.12em;text-transform:uppercase}h1{margin:4px 0 4px;font-size:24px;line-height:1.15}.lede{margin:0;color:var(--muted)}.toolbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:16px 0 10px}.toolbar button,.toolbar select{border:1px solid var(--line);border-radius:9px;padding:9px 11px;background:var(--bg);color:inherit}.toolbar button{cursor:pointer;font-weight:650}.toolbar .primary{border-color:var(--accent);background:var(--accent);color:var(--button)}.toolbar button:disabled{opacity:.58;cursor:wait}.toolbar select{min-width:150px}.status{min-height:22px;margin:4px 0;color:var(--muted)}.error{color:var(--bad)}.summary{display:flex;gap:7px;flex-wrap:wrap;margin:12px 0}.pill{display:inline-block;padding:4px 8px;border-radius:999px;background:var(--soft);color:var(--accent);font-size:12px}.pill.good{color:var(--good);background:rgba(47,133,90,.14)}.pill.bad{color:var(--bad);background:rgba(180,35,24,.12)}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:10px}.panel{padding:13px;border:1px solid var(--line);border-radius:12px;background:var(--bg)}.panel h2,.panel h3{margin:0 0 8px;font-size:16px}.panel p{margin:4px 0;color:var(--muted)}.chart{min-height:128px;margin:8px 0;padding:7px;border:1px solid var(--line);border-radius:10px;background:var(--bg);overflow:hidden}.chart svg{display:block;width:100%;height:115px}.chart-empty{display:grid;place-items:center;height:112px;color:var(--muted);font-size:12px}.axis{fill:var(--muted);font-size:10px}.line-a{fill:none;stroke:#4f7cac;stroke-width:2.2}.line-b{fill:none;stroke:#b56a3c;stroke-width:2.2}.table-wrap{max-height:460px;overflow:auto;border:1px solid var(--line);border-radius:11px;background:var(--bg)}table{width:100%;border-collapse:collapse;min-width:600px}th,td{padding:9px 11px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}th{position:sticky;top:0;background:var(--panel);color:var(--muted);font-size:12px;z-index:1}tr:last-child td{border-bottom:0}.muted{color:var(--muted)}.empty{padding:24px;text-align:center;color:var(--muted)}.source-list{display:grid;gap:7px}.source-row{display:flex;justify-content:space-between;gap:10px;align-items:center;padding:8px 10px;border-radius:9px;background:var(--soft)}.setting{display:grid;grid-template-columns:minmax(130px,1fr) minmax(150px,1fr);gap:9px;align-items:center;padding:9px 0;border-bottom:1px solid var(--line)}.setting:last-child{border-bottom:0}.setting label{font-weight:650}.setting small{display:block;color:var(--muted);font-weight:400}.setting input,.setting select{width:100%;border:1px solid var(--line);border-radius:8px;padding:8px;background:var(--bg);color:inherit}.setting input:disabled,.setting select:disabled{opacity:.6}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:12px}.actions button{border:1px solid var(--accent);border-radius:8px;padding:8px 12px;background:var(--accent);color:var(--button);cursor:pointer}.inline-action{border:1px solid var(--accent);border-radius:7px;padding:5px 8px;background:var(--accent);color:var(--button);cursor:pointer;font-size:12px}.actions button:disabled{opacity:.6;cursor:wait}@media(max-width:650px){.shell{padding:10px}.card{padding:14px}.setting{grid-template-columns:1fr}.table-wrap{margin:0 -2px}}
@media(prefers-color-scheme:dark){:root{--bg:#202225;--panel:#2b2e32;--line:#474c52;--ink:#eceff2;--muted:#9da3aa;--accent:#c7b07a;--soft:rgba(199,176,122,.17);--good:#83c995;--bad:#ff9a93;--button:#241f14;--shadow:0 10px 26px rgba(0,0,0,.34)}} @media(prefers-color-scheme:dark){:root{--bg:#202225;--panel:#2b2e32;--line:#474c52;--ink:#eceff2;--muted:#9da3aa;--accent:#c7b07a;--soft:rgba(199,176,122,.17);--good:#83c995;--bad:#ff9a93;--button:#241f14;--shadow:0 10px 26px rgba(0,0,0,.34)}}
</style> </style>
</head> </head>
@@ -14,36 +14,127 @@
<main class="shell"><section class="card"> <main class="shell"><section class="card">
<div class="eyebrow">SHD MCP · controller observability</div> <div class="eyebrow">SHD MCP · controller observability</div>
<h1 id="title">Контроллеры</h1><p class="lede" id="lede"></p> <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">Автоматически</option><option value="native">Нативный источник</option><option value="influx">Influx/Grafana</option><option value="all">Все источники</option></select><select id="preset" hidden><option value="co2-ventilation">CO₂ → вентиляция</option><option value="temperature-heating">Температура → отопление</option><option value="humidity-ventilation">Влажность → вентиляция</option><option value="custom">Своя пара</option></select></div> <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><option value="all" data-i18n="sourceAll">Все источники</option></select><select id="preset" hidden><option value="co2-ventilation" data-i18n="presetCo2Ventilation">CO₂ → вентиляция</option><option value="temperature-heating" data-i18n="presetTemperatureHeating">Температура → отопление</option><option value="humidity-ventilation" data-i18n="presetHumidityVentilation">Влажность → вентиляция</option><option value="custom" data-i18n="presetCustom">Своя пара</option></select></div>
<div id="status" class="status" role="status" aria-live="polite"></div><div id="summary" class="summary"></div><div id="content"></div> <div id="status" class="status" role="status" aria-live="polite"></div><div id="summary" class="summary"></div><div id="content"></div>
</section></main> </section></main>
<script> <script>
(function(){'use strict'; (function(){'use strict';
var T={ru:{history:['История контроллера','Временные точки и происхождение данных.'],compare:['Сравнение рядов','Универсальное сравнение временных рядов на общей шкале.'],settings:['Экспертные настройки','Поля доступны для записи только при явном разрешении источника.'],topology:['Топология контроллеров','Все контроллеры проекта; inventory устройств загружается отдельно.'],health:['Здоровье telemetry','Состояние нативного источника и Influx/Grafana.'],audit:['Аудит операций','Команды, подтверждения и readback по source-контроллеру.'],inventorySeparate:'Инвентарь устройств загружается отдельным запросом по выбранному контроллеру.',refresh:'Обновить',loading:'Загрузка…',empty:'Нет данных для отображения.',records:'записей',native:'Нативный источник',influx:'Influx/Grafana',available:'доступен',unavailable:'недоступен',configured:'настроен',points:'точек',source:'Источник',time:'Время',value:'Значение',name:'Название',controller:'Контроллер',device:'Устройство',room:'Комната',type:'Тип',status:'Статус',error:'Ошибка',readonly:'Только чтение',save:'Сохранить',saved:'Настройки сохранены.',noWritable:'Нет разрешённых writable-параметров.',required:'Обязательное поле',unknown:'Неизвестный формат ответа.',fail:'Не удалось загрузить данные.',id:'ID',action:'Действие',requested:'Запрошено',actual:'Фактически',revision:'Ревизия',checked:'Проверено'},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; device inventory is loaded separately.'],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',native:'Native source',influx:'Influx/Grafana',available:'available',unavailable:'unavailable',configured:'configured',points:'points',source:'Source',time:'Time',value:'Value',name:'Name',controller:'Controller',device:'Device',room:'Room',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'}}; var T={
var state={mode:'history',config:{},data:null,busy:false,lang:(String((window.openai&&window.openai.locale)||document.documentElement.lang||'ru').toLowerCase().indexOf('en')===0?'en':'ru')};var pending=new Map(),nextId=1; 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:'выборок',
sourceAuto:'Автоматически',sourceNative:'Нативный источник',sourceInflux:'Influx/Grafana',sourceAll:'Все источники',
presetCo2Ventilation:'CO₂ → вентиляция',presetTemperatureHeating:'Температура → отопление',presetHumidityVentilation:'Влажность → вентиляция',presetCustom:'Своя пара',
native:'Нативный источник',influx:'Influx/Grafana',available:'доступен',unavailable:'недоступен',
configured:'настроен',points:'точек',source:'Источник',sourceDetails:'Источник и качество',
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',
sourceAuto:'Automatic',sourceNative:'Native source',sourceInflux:'Influx/Grafana',sourceAll:'All sources',
presetCo2Ventilation:'CO₂ → ventilation',presetTemperatureHeating:'Temperature → heating',presetHumidityVentilation:'Humidity → ventilation',presetCustom:'Custom pair',
native:'Native source',influx:'Influx/Grafana',available:'available',unavailable:'unavailable',
configured:'configured',points:'points',source:'Source',sourceDetails:'Source and quality',
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'
}
};
var state={mode:'history',config:{},data:null,busy:false,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'),preset:document.getElementById('preset'),status:document.getElementById('status'),summary:document.getElementById('summary'),content:document.getElementById('content')}; var el={title:document.getElementById('title'),lede:document.getElementById('lede'),refresh:document.getElementById('refresh'),source:document.getElementById('source'),preset:document.getElementById('preset'),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 t(k){return(T[state.lang]&&T[state.lang][k])||k}
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 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=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 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=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);state.config.sourceArgs=obj(w.sourceArgs)?w.sourceArgs:(obj(m.sourceArgs)?m.sourceArgs:{});return m} 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);return source.meta&&source.meta.widget&&source.data!==undefined?source.data:source} function dataFrom(p){var source=unwrap(p);metaFrom(source);return source.meta&&source.meta.widget&&source.data!==undefined?source.data:source}
function labels(){var heading=T[state.lang][state.mode]||T[state.lang].history;el.title.textContent=heading[0];el.lede.textContent=heading[1];el.refresh.textContent=t('refresh');el.source.hidden=!['history','compare','health'].includes(state.mode);el.preset.hidden=state.mode!=='compare';if(state.mode==='compare'&&state.config.preset)el.preset.value=state.config.preset} function translateOptions(select){select.querySelectorAll('[data-i18n]').forEach(function(option){option.textContent=t(option.getAttribute('data-i18n'))})}
function summary(items){el.summary.replaceChildren();(items||[]).forEach(function(x){var s=document.createElement('span');s.className='pill '+(x.kind||'');s.textContent=x.value;el.summary.append(s)})} function labels(){var heading=T[state.lang][state.mode]||T[state.lang].history;document.documentElement.lang=state.lang;el.title.textContent=heading[0];el.lede.textContent=heading[1];el.refresh.textContent=t('refresh');translateOptions(el.source);translateOptions(el.preset);el.source.hidden=!['history','compare','health','statistics'].includes(state.mode);el.preset.hidden=state.mode!=='compare';if(state.mode==='compare'&&state.config.preset)el.preset.value=state.config.preset;if(state.config.sourceArgs&&state.config.sourceArgs.source)el.source.value=state.config.sourceArgs.source}
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 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 kindLabel(value){return value==='binary'?t('binary'):value==='numeric'?t('numeric'):text(value)}
function boolLabel(value){return value?t('yes'):t('no')}
function chart(points,cls){var nums=(Array.isArray(points)?points:[]).map(function(p){return Number(p&&p.value)}).filter(function(v){return Number.isFinite(v)});if(!nums.length)return '<div class="chart-empty">'+esc(t('empty'))+'</div>';var w=700,h=112,pad=18,min=Math.min.apply(null,nums),max=Math.max.apply(null,nums);if(min===max){min-=1;max+=1}var coords=nums.map(function(v,i){var x=pad+(w-pad*2)*(nums.length===1?.5:i/(nums.length-1));var y=h-pad-(h-pad*2)*(v-min)/(max-min);return x.toFixed(1)+','+y.toFixed(1)}).join(' ');return '<svg viewBox="0 0 '+w+' '+h+'" role="img" aria-label="'+esc(t('value'))+'"><polyline class="'+(cls||'line-a')+'" points="'+coords+'"/><text class="axis" x="4" y="14">'+esc(String(max))+'</text><text class="axis" x="4" y="'+(h-3)+'">'+esc(String(min))+'</text></svg>'} function chart(points,cls){var nums=(Array.isArray(points)?points:[]).map(function(p){return Number(p&&p.value)}).filter(function(v){return Number.isFinite(v)});if(!nums.length)return '<div class="chart-empty">'+esc(t('empty'))+'</div>';var w=700,h=112,pad=18,min=Math.min.apply(null,nums),max=Math.max.apply(null,nums);if(min===max){min-=1;max+=1}var coords=nums.map(function(v,i){var x=pad+(w-pad*2)*(nums.length===1?.5:i/(nums.length-1));var y=h-pad-(h-pad*2)*(v-min)/(max-min);return x.toFixed(1)+','+y.toFixed(1)}).join(' ');return '<svg viewBox="0 0 '+w+' '+h+'" role="img" aria-label="'+esc(t('value'))+'"><polyline class="'+(cls||'line-a')+'" points="'+coords+'"/><text class="axis" x="4" y="14">'+esc(String(max))+'</text><text class="axis" x="4" y="'+(h-3)+'">'+esc(String(min))+'</text></svg>'}
function card(title,body){var n=document.createElement('section');n.className='panel';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 card(title,body){var n=document.createElement('section');n.className='panel';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');td.textContent=text(v);r.append(td)});tb.append(r)});tab.append(tb);wrap.append(tab);return wrap} 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 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(s.name==='native'?t('native'):t('influx'))+'</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[p.time,p.value,p.name||p.topic||p.channel,d.telemetry&&d.telemetry.provider||d.selected_history_source]})));else el.content.append(document.createTextNode(t('empty')))} function empty(){var n=document.createElement('div');n.className='empty';n.textContent=t('empty');return n}
function renderCompare(d){var list=Array.isArray(d.series)?d.series:[];summary([{value:(d.preset||'custom')},{value:list.length+' '+t('records')},{value:text(d.from)+' → '+text(d.to)}]);var grid=document.createElement('div');grid.className='grid';list.forEach(function(s,i){var source=s.selected_history_source||s.history_source||'';var sourceLabel=source==='native'?t('native'):(source==='influx'?t('influx'):text(source||'—'));var p=card(s.label||s.id||t('records'),'<p>'+esc(s.controller||'')+' · '+esc(s.item_id||'')+' '+pill(sourceLabel,source?'':'bad')+' '+pill(s.available?t('available'):text(s.error_code||t('unavailable')),s.available?'good':'bad')+'</p><div class="chart">'+chart(s.points,i%2?'line-b':'line-a')+'</div>');grid.append(p)});el.content.append(grid);if(!list.length)el.content.append(document.createTextNode(t('empty')))} function metricGrid(items){var grid=document.createElement('div');grid.className='grid';items.forEach(function(item){grid.append(card(item[0],'<p>'+esc(item[1])+'</p>'))});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[p.time,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:(d.preset||'custom')},{value:list.length+' '+t('records')},{value:text(d.from)+' → '+text(d.to)}]);var grid=document.createElement('div');grid.className='grid';list.forEach(function(s,i){var source=s.selected_history_source||s.history_source||'';var p=card(s.label||s.id||t('records'),'<p>'+esc(s.controller||'')+' · '+esc(s.item_id||'')+' '+pill(sourceLabel(source),source?'':'bad')+' '+pill(s.available?t('available'):text(s.error_code||t('unavailable')),s.available?'good':'bad')+'</p><div class="chart">'+chart(s.points,i%2?'line-b':'line-a')+'</div>');grid.append(p)});el.content.append(grid);if(!list.length)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 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 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 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 renderTopology(d){var controllers=Array.isArray(d.controllers)?d.controllers:[];summary([{value:controllers.length+' '+t('controller')},{value:t('inventorySeparate')}]);el.content.append(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]})))} 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 renderHealth(d){var list=Array.isArray(d.controllers)?d.controllers:[d];summary([{value:list.length+' '+t('controller')}]);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(s.name==='native'?t('native'):t('influx'))+'</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 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 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[x.created_at,x.item_id,x.action,x.status,text(x.requested_value),text(x.actual_readback)]})):document.createTextNode(t('empty')))} 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 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==='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 liveRows(d){var rows=[];var controllers=Array.isArray(d.controllers)?d.controllers:[];if(controllers.length){controllers.forEach(function(c){(Array.isArray(c.states)?c.states:[]).forEach(function(s){rows.push([c.controller_name||c.controller,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])})})}else{(Array.isArray(d.states)?d.states:[]).forEach(function(s){rows.push([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){var s=obj(d.summary)?d.summary:{};var available=d.statistics_available===true;summary([{value:kindLabel(d.statistics_kind)},{value:(s.sample_count||0)+' '+t('samples')},{value:t('source')+': '+sourceLabel(d.selected_history_source||d.history_source_requested),kind:d.selected_history_source?'good':'bad'},{value:text(d.from)+' → '+text(d.to)}]);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||'—',d.from,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[i.start,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'?[b.bucket_start,formatNumber(b.event_count),formatNumber(b.transition_count),formatNumber(b.period_count),formatDuration(b.active_seconds)]:[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[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[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 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(state.mode!=='settings'&&el.source.value&&['history','compare','health'].includes(state.mode))args.source=el.source.value;if(state.mode==='compare'){args.preset=el.preset.value;var groups={"co2-ventilation":["co2","ventilation"],"temperature-heating":["temperature","heating"],"humidity-ventilation":["humidity","ventilation"]}[args.preset];if(groups&&Array.isArray(args.series)){var selected=args.series.filter(function(s){return groups.includes(String(s.group||'').toLowerCase())});if(selected.length)args.series=selected}}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})} 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;if(state.mode==='compare'){args.preset=el.preset.value;var groups={"co2-ventilation":["co2","ventilation"],"temperature-heating":["temperature","heating"],"humidity-ventilation":["humidity","ventilation"]}[args.preset];if(groups&&Array.isArray(args.series)){var selected=args.series.filter(function(s){return groups.includes(String(s.group||'').toLowerCase())});if(selected.length)args.series=selected}}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})}
el.source.onchange=refresh;el.preset.onchange=refresh;el.refresh.onclick=refresh; labels();el.source.onchange=refresh;el.preset.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)}}}); 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:'2025-11-25',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()} rpc('ui/initialize',{protocolVersion:'2025-11-25',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()}
}()); }());
@@ -5,11 +5,11 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 22446, "bytes": 36142,
"gzipBytes": 7762, "gzipBytes": 11206,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/controller-observability/v1.html", "uri": "ui://shd/controller-observability/v1.html",
"sha256": "de6f1c6e07d74bc58555f41a22e9a0e8a3942577ffce5773f9450707cb7ca440" "sha256": "74cc8cd974ddfb0563b213166398cf37e98cd477e010c9fbcc444324b5366a5c"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
@@ -1 +1 @@
e431183d3b959601687632ea36b207fc88c85f88c69c2d3abdd342be411db50c index.html 2704ffa6778cef26875f187623c2e58e3d0a76085b5369838b9abc7da137a789 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/crm-funnel/v1.html. This artifact is served by the SHD MCP backend as ui://shd/crm-funnel/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only CRM deal funnel grouped by stage. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77269, "bytes": 78910,
"gzipBytes": 20604, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/crm-funnel/v1.html", "uri": "ui://shd/crm-funnel/v1.html",
"sha256": "e431183d3b959601687632ea36b207fc88c85f88c69c2d3abdd342be411db50c" "sha256": "2704ffa6778cef26875f187623c2e58e3d0a76085b5369838b9abc7da137a789"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/crm-funnel/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
6fde9e254a5ff34e4cc1110f729b35661bc08ece41cae8d7c9783549409a3d9c index.html f859c752e2ae3eacdc7a4fb18ec460cbfddb62a327a7d636b76b5482ec4ad7fd index.html
@@ -1,5 +1,5 @@
# CRM widget # CRM widget
This artifact is served by the SHD MCP backend as `ui://shd/crm/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/crm/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -7,14 +7,17 @@
"file": "index.html", "file": "index.html",
"uri": "ui://shd/crm/v1.html", "uri": "ui://shd/crm/v1.html",
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"bytes": 46636, "bytes": 78896,
"sha256": "6fde9e254a5ff34e4cc1110f729b35661bc08ece41cae8d7c9783549409a3d9c", "sha256": "f859c752e2ae3eacdc7a4fb18ec460cbfddb62a327a7d636b76b5482ec4ad7fd",
"gzipBytes": 12425 "gzipBytes": 21186
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/crm/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
d051f38e8d81151a3269d16effae3bca502527680a106c7f9b9d7942b9f7446c index.html 273dd8aaf6ef1e9ead2312484a7a7d08bfe3b0258cb0e9eb71f9061a4b777922 index.html
@@ -1,5 +1,5 @@
# Discussions widget # Discussions widget
This artifact is served by the SHD MCP backend as `ui://shd/discussions/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/discussions/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The widget provides topics and read-only message details. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 49141, "bytes": 78912,
"gzipBytes": 14385, "gzipBytes": 21185,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/discussions/v1.html", "uri": "ui://shd/discussions/v1.html",
"sha256": "d051f38e8d81151a3269d16effae3bca502527680a106c7f9b9d7942b9f7446c" "sha256": "273dd8aaf6ef1e9ead2312484a7a7d08bfe3b0258cb0e9eb71f9061a4b777922"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/discussions/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
6964112ce1ae2ec25a736709bc00ad0a7335177ec98547ad48d672834e491760 index.html c99dc13e4cf8302699f9ae8cdf84462b999e20eee49b2baf18b620fb083f9e6f index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/documents-completeness/v1.html. This artifact is served by the SHD MCP backend as ui://shd/documents-completeness/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only Terms and document completeness dashboard. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77329, "bytes": 78934,
"gzipBytes": 20618, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/documents-completeness/v1.html", "uri": "ui://shd/documents-completeness/v1.html",
"sha256": "6964112ce1ae2ec25a736709bc00ad0a7335177ec98547ad48d672834e491760" "sha256": "c99dc13e4cf8302699f9ae8cdf84462b999e20eee49b2baf18b620fb083f9e6f"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/documents-completeness/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
bc1b879e77c078049a8dcbf237792eb00c2975ad7b419c8b12aea0f60930a3a8 index.html 761ab899858efa595426ed1eda8ccac6315ffec5bad524503f74bf0c1853a20f index.html
@@ -1,5 +1,5 @@
# Документы widget # Документы widget
This artifact is served by the SHD MCP backend as `ui://shd/documents/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/documents/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -7,14 +7,17 @@
"file": "index.html", "file": "index.html",
"uri": "ui://shd/documents/v1.html", "uri": "ui://shd/documents/v1.html",
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"bytes": 46686, "bytes": 78908,
"sha256": "bc1b879e77c078049a8dcbf237792eb00c2975ad7b419c8b12aea0f60930a3a8", "sha256": "761ab899858efa595426ed1eda8ccac6315ffec5bad524503f74bf0c1853a20f",
"gzipBytes": 12419 "gzipBytes": 21188
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/documents/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
e410bd6143c07e677ef23ede0be1d63e17587bacaf16732334701b4a43399e5d index.html 5ca14633d758f98ecbfeb3b20cee1a09140dbaf724e1e12e28bf5ce0c89bd3de index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/files/v1.html. This artifact is served by the SHD MCP backend as ui://shd/files/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides Project file register with safe read-only links. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 53667, "bytes": 78900,
"gzipBytes": 15488, "gzipBytes": 21187,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/files/v1.html", "uri": "ui://shd/files/v1.html",
"sha256": "e410bd6143c07e677ef23ede0be1d63e17587bacaf16732334701b4a43399e5d" "sha256": "5ca14633d758f98ecbfeb3b20cee1a09140dbaf724e1e12e28bf5ce0c89bd3de"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/files/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
20231dba4f8d99b89d33bfe8a9247d3d8728d17fd75bf07cd10ed9950ad0913c index.html 57681855fe46c8bd436a53b851f1c6ee646014b65ff93913af40f0d0f118e85c index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/finance-dashboard/v1.html. This artifact is served by the SHD MCP backend as ui://shd/finance-dashboard/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only finance totals, statuses and records. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77283, "bytes": 78924,
"gzipBytes": 20601, "gzipBytes": 21187,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/finance-dashboard/v1.html", "uri": "ui://shd/finance-dashboard/v1.html",
"sha256": "20231dba4f8d99b89d33bfe8a9247d3d8728d17fd75bf07cd10ed9950ad0913c" "sha256": "57681855fe46c8bd436a53b851f1c6ee646014b65ff93913af40f0d0f118e85c"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/finance-dashboard/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
9602680407f0da06e87d71ad3374dbd4602d6ca6124f19983647b207c33c5b6e index.html 7b1a5c129a4995ca3b8850568d9eec5c1fb949a132efbfcd6658fc94a03747d3 index.html
@@ -1,5 +1,5 @@
# Финансы widget # Финансы widget
This artifact is served by the SHD MCP backend as `ui://shd/finance/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/finance/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -7,14 +7,17 @@
"file": "index.html", "file": "index.html",
"uri": "ui://shd/finance/v1.html", "uri": "ui://shd/finance/v1.html",
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"bytes": 46724, "bytes": 78904,
"sha256": "9602680407f0da06e87d71ad3374dbd4602d6ca6124f19983647b207c33c5b6e", "sha256": "7b1a5c129a4995ca3b8850568d9eec5c1fb949a132efbfcd6658fc94a03747d3",
"gzipBytes": 12444 "gzipBytes": 21188
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/finance/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
c761b817f26067ed2697d6c5da3f99a0e962dbf920a1c13ff24165dde910af7c index.html 6e92d2e27b29d3b90c4ca66c23891fd32fcb07e3a467f40009bd5dba916a9fc0 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/gitea-board/v1.html. This artifact is served by the SHD MCP backend as ui://shd/gitea-board/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only Gitea issues grouped by state. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77271, "bytes": 78912,
"gzipBytes": 20602, "gzipBytes": 21188,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/gitea-board/v1.html", "uri": "ui://shd/gitea-board/v1.html",
"sha256": "c761b817f26067ed2697d6c5da3f99a0e962dbf920a1c13ff24165dde910af7c" "sha256": "6e92d2e27b29d3b90c4ca66c23891fd32fcb07e3a467f40009bd5dba916a9fc0"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/gitea-board/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
dac317642f83f1a2599fff7041e340f4fdc5620733baaaa426a69edf5d9c7442 index.html da4af974cf16b4f442d70ecaa6e634b76eb0ff63817a401c3a03fc65f86b1ad6 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/gitea/v1.html. This artifact is served by the SHD MCP backend as ui://shd/gitea/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides Read-only issue register for allowed targets. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 53667, "bytes": 78900,
"gzipBytes": 15487, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/gitea/v1.html", "uri": "ui://shd/gitea/v1.html",
"sha256": "dac317642f83f1a2599fff7041e340f4fdc5620733baaaa426a69edf5d9c7442" "sha256": "da4af974cf16b4f442d70ecaa6e634b76eb0ff63817a401c3a03fc65f86b1ad6"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/gitea/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
ea7d3bc74188e3adc144c07404cd6a26779b42d4cc3fb6ffc83f113fb64288bc index.html bfcc6032e183d611579240950bd97bc7747222f55cb4ccf01905ead46b0bf9bc index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/inventory-warnings/v1.html. This artifact is served by the SHD MCP backend as ui://shd/inventory-warnings/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only low-stock and critical inventory dashboard. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77285, "bytes": 78926,
"gzipBytes": 20606, "gzipBytes": 21188,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/inventory-warnings/v1.html", "uri": "ui://shd/inventory-warnings/v1.html",
"sha256": "ea7d3bc74188e3adc144c07404cd6a26779b42d4cc3fb6ffc83f113fb64288bc" "sha256": "bfcc6032e183d611579240950bd97bc7747222f55cb4ccf01905ead46b0bf9bc"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/inventory-warnings/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
fda7797f99a07415ecc4879f44a04c936953c055f79bdee2e1f3e88513fdff7d index.html 26bd7d21dd1613d99919bc0048a697e00ce2f302be194d8783ba43d9fced2023 index.html
@@ -1,5 +1,5 @@
# Inventory widget # Inventory widget
This artifact is served by the SHD MCP backend as `ui://shd/inventory/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/inventory/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The widget provides stock quantities and read-only item details. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 49137, "bytes": 78908,
"gzipBytes": 14388, "gzipBytes": 21187,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/inventory/v1.html", "uri": "ui://shd/inventory/v1.html",
"sha256": "fda7797f99a07415ecc4879f44a04c936953c055f79bdee2e1f3e88513fdff7d" "sha256": "26bd7d21dd1613d99919bc0048a697e00ce2f302be194d8783ba43d9fced2023"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/inventory/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
ca76aa4e10fe1a5f2d167fe8876a3d009aca9a2e386cde5f8743ede370272221 index.html b03ee937b1537989b9ca852ed17b9033e34968010947b4cadfc78bcb5781d1a3 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/notes-tree/v1.html. This artifact is served by the SHD MCP backend as ui://shd/notes-tree/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides a read-only Notes spaces and hierarchy tree. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 77269, "bytes": 78910,
"gzipBytes": 20607, "gzipBytes": 21189,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/notes-tree/v1.html", "uri": "ui://shd/notes-tree/v1.html",
"sha256": "ca76aa4e10fe1a5f2d167fe8876a3d009aca9a2e386cde5f8743ede370272221" "sha256": "b03ee937b1537989b9ca852ed17b9033e34968010947b4cadfc78bcb5781d1a3"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/notes-tree/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
cb4881dca8cf3f999b27a540239f9a4d17204bc4ba5e65a4bf2f5ed0e7810e23 index.html a2114b5a6014c9d266b7277e29270a916765c6d6b4b7c5625610762b46b67eae index.html
@@ -1,5 +1,5 @@
# Notes widget # Notes widget
This artifact is served by the SHD MCP backend as `ui://shd/notes/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/notes/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The widget provides page register and read-only page details. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 49129, "bytes": 78900,
"gzipBytes": 14384, "gzipBytes": 21186,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/notes/v1.html", "uri": "ui://shd/notes/v1.html",
"sha256": "cb4881dca8cf3f999b27a540239f9a4d17204bc4ba5e65a4bf2f5ed0e7810e23" "sha256": "a2114b5a6014c9d266b7277e29270a916765c6d6b4b7c5625610762b46b67eae"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/notes/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
c19707e63a55e05ee4000f6a3e0b4ea9e5955623899ed21213e2a6cb4e6d3c74 index.html e7f8ffe04b2c043e46930d560653870c77725f5748585514fff6266c4dfd7584 index.html
@@ -1,5 +1,5 @@
# Notifications widget # Notifications widget
This artifact is served by the SHD MCP backend as `ui://shd/notifications/v1.html`. This artifact is served by the SHD MCP backend as ui://shd/notifications/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps `window.openai` as a compatibility extension. It has no external script or network dependency. The widget provides notifications and read-only event details. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.
File diff suppressed because one or more lines are too long
@@ -5,16 +5,19 @@
"resources": [ "resources": [
{ {
"file": "index.html", "file": "index.html",
"bytes": 49145, "bytes": 78916,
"gzipBytes": 14386, "gzipBytes": 21183,
"mediaType": "text/html;profile=mcp-app", "mediaType": "text/html;profile=mcp-app",
"uri": "ui://shd/notifications/v1.html", "uri": "ui://shd/notifications/v1.html",
"sha256": "c19707e63a55e05ee4000f6a3e0b4ea9e5955623899ed21213e2a6cb4e6d3c74" "sha256": "e7f8ffe04b2c043e46930d560653870c77725f5748585514fff6266c4dfd7584"
} }
], ],
"license": "../../../../../LICENSE", "license": "../../../../../LICENSE",
"source": { "source": {
"repository": "https://github.com/bulava92/shd-mcp-plugin", "repository": "https://github.com/bulava92/shd-mcp-plugin",
"path": "plugins/shd-mcp-plugin/widgets/notifications/v1/index.html" "path": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget.html",
"generatedBy": "scripts/generate-widget-artifacts.mjs",
"engine": "shd-universal-v1",
"config": "plugins/shd-mcp-plugin/widgets/_shared/universal-widget-config.json"
} }
} }
@@ -1 +1 @@
333f9d1635a1bba89ae8adf4af8c29b011df20cd781e434a42f9c58e413e8999 index.html 1b4f9c6696b76cd79064fa5de6b3a43892be21c7b3cb9e635e9ce56cc9b90055 index.html
@@ -2,4 +2,4 @@
This artifact is served by the SHD MCP backend as ui://shd/organizations/v1.html. This artifact is served by the SHD MCP backend as ui://shd/organizations/v1.html.
The HTML is standalone, uses the MCP Apps bridge first, and keeps window.openai as a compatibility extension. It has no external script or network dependency. The widget provides Organization register with safe membership details. The standalone HTML is generated from `widgets/_shared/universal-widget.html` and `widgets/_shared/universal-widget-config.json` by `scripts/generate-widget-artifacts.mjs` using the `shd-universal-v1` engine. It uses the MCP Apps bridge first and keeps `window.openai` as a compatibility extension; it has no external script or network dependency. The module supplies its data and view configuration through the render-tool metadata.

Some files were not shown because too many files have changed in this diff Show More