@@ -0,0 +1,91 @@
<!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 10 px 26 px rgba ( 31 , 41 , 55 , .1 ) }
* { box-sizing : border-box } body { margin : 0 ; background : var ( - - bg ) ; color : var ( - - ink ) ; font : 14 px / 1.45 system-ui , - apple-system , BlinkMacSystemFont , "Segoe UI" , sans-serif } . shell { max-width : 980 px ; margin : auto ; padding : 22 px } . card { padding : 20 px ; border : 1 px solid var ( - - line ) ; border-radius : 18 px ; background : var ( - - surface ) ; box-shadow : var ( - - shadow ) }
. eyebrow { color : var ( - - muted ) ; font-size : 11 px ; font-weight : 700 ; letter-spacing : .12 em ; text-transform : uppercase } h1 { margin : 4 px 0 5 px ; font-size : 25 px ; line-height : 1.15 } . lede { margin : 0 ; color : var ( - - muted ) } . summary { display : flex ; gap : 8 px ; flex-wrap : wrap ; margin : 18 px 0 14 px } . summary span { padding : 5 px 9 px ; border : 1 px solid var ( - - line ) ; border-radius : 999 px ; background : var ( - - hover ) ; color : var ( - - muted ) } . summary strong { color : var ( - - ink ) }
. toolbar { display : flex ; gap : 8 px ; margin-bottom : 10 px } . search { min-width : 0 ; flex : 1 ; padding : 10 px 12 px ; border : 1 px solid var ( - - line ) ; border-radius : 10 px ; background : var ( - - bg ) ; color : inherit ; outline : 0 } . search : focus { border-color : var ( - - accent ) ; box-shadow : 0 0 0 3 px var ( - - soft ) } button { cursor : pointer } . refresh { padding : 10 px 14 px ; border : 1 px solid var ( - - accent ) ; border-radius : 10 px ; background : var ( - - accent ) ; color : var ( - - button ) ; font-weight : 650 } . refresh : disabled { opacity : .6 ; cursor : wait } . status { min-height : 22 px ; color : var ( - - muted ) } . error { color : var ( - - bad ) }
. table-wrap { max-height : 460 px ; overflow : auto ; border : 1 px solid var ( - - line ) ; border-radius : 12 px ; background : var ( - - bg ) } table { width : 100 % ; border-collapse : collapse ; min-width : 560 px } th , td { padding : 11 px 13 px ; border-bottom : 1 px solid var ( - - line ) ; text-align : left ; vertical-align : top } th { position : sticky ; top : 0 ; background : var ( - - hover ) ; color : var ( - - muted ) ; font-size : 12 px ; 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 : 12 px ; margin-top : 2 px } . pill { display : inline-block ; padding : 3 px 8 px ; border-radius : 999 px ; background : var ( - - soft ) ; color : var ( - - accent ) ; font-size : 12 px ; 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 : 24 px ; text-align : center ; color : var ( - - muted ) }
. details { margin-top : 14 px ; padding : 16 px ; border : 1 px solid var ( - - line ) ; border-radius : 12 px ; background : var ( - - bg ) } . details [ hidden ] { display : none } . detail-head { display : flex ; justify-content : space-between ; gap : 12 px ; align-items : start } . details h2 { margin : 0 ; font-size : 18 px } . close { border : 0 ; background : transparent ; color : var ( - - muted ) ; font-size : 20 px } . grid { display : grid ; grid-template-columns : repeat ( auto - fit , minmax ( 150 px , 1 fr ) ) ; gap : 10 px ; margin-top : 14 px } . detail { padding : 9 px ; border-radius : 8 px ; background : var ( - - surface ) } . label { display : block ; color : var ( - - muted ) ; font-size : 11 px ; margin-bottom : 3 px } . value { overflow-wrap : anywhere ; white-space : pre-wrap } @ media ( max-width : 640px ) { . shell { padding : 12 px } . card { padding : 15 px } . toolbar { display : grid ; grid-template-columns : minmax ( 0 , 1 fr ) 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 10 px 26 px rgba ( 0 , 0 , 0 , .34 ) } }
< / style >
< / head >
< body >
< main class = "shell" data-widget-module = "discussions" > < 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 >
< 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 = 'discussions' ;
var C = { ru : { refresh : 'Обновить' , search : 'Поиск по названию, статусу или ID' , loading : 'Загрузка…' , empty : 'Нет данных для отображения.' , records : 'записей' , status : 'Статус' , date : 'Дата' , value : 'Детали' , id : 'ID' , details : 'Детали' , close : 'Закрыть' , load : 'Не удалось загрузить данные.' , detail : 'Не удалось загрузить детали.' , context : 'Для обновления нужен контекст проекта или базы' } , 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 project or base context is required to refresh.' } } ;
var M = {
discussions : { ru : [ 'Обсуждения' , 'Темы обсуждений с поиском и просмотром сообщений.' , 'Тема' , 'Обновлено' , 'Сообщение' ] , en : [ 'Discussions' , 'Discussion topics with search and message details.' , 'Topic' , 'Updated' , 'Message' ] } ,
notifications : { ru : [ 'Уведомления' , 'Последние уведомления и события модулей.' , 'Уведомление' , 'Время' , 'Источник' ] , en : [ 'Notifications' , 'Recent notifications and module events.' , 'Notification' , 'Time' , 'Source' ] } ,
scheduling : { ru : [ 'Расписание' , 'Ближайшие события и записи из расписания.' , 'Событие' , 'Начало' , 'Участник' ] , en : [ 'Scheduling' , 'Upcoming events and bookings from scheduling.' , 'Event' , 'Starts' , 'Guest' ] } ,
inventory : { ru : [ 'Склад' , 'Складские позиции, остатки и статусы.' , 'Позиция' , 'Обновлено' , 'Количество' ] , en : [ 'Inventory' , 'Stock items, quantities and statuses.' , 'Item' , 'Updated' , 'Quantity' ] } ,
agents : { ru : [ 'Агенты и контроллеры' , 'Доступные агенты, контроллеры и их текущее состояние.' , 'Агент или контроллер' , 'Проверено' , 'Тип' ] , en : [ 'Agents & Controllers' , 'Available agents, controllers and their current state.' , 'Agent or controller' , 'Checked' , 'Type' ] } ,
'status-page' : { ru : [ 'Status Page' , 'Состояние доступности сервисов и мониторов.' , 'Монитор' , 'Проверено' , 'Доступность' ] , en : [ 'Status Page' , 'Service availability and monitor state.' , 'Monitor' , 'Checked' , 'Availability' ] } ,
'project-db' : { ru : [ 'Project DB' , 'Каталог таблиц Project DB с просмотром схемы.' , 'Таблица' , 'Обновлено' , 'Записей' ] , en : [ 'Project DB' , 'Project DB table catalog with schema details.' , 'Table' , 'Updated' , 'Records' ] } ,
notes : { ru : [ 'Заметки' , 'Страницы Notes с поиском и просмотром содержимого.' , 'Страница' , 'Обновлено' , 'Пространство' ] , en : [ 'Notes' , 'Notes pages with search and content details.' , 'Page' , 'Updated' , 'Space' ] }
} ;
var S = {
discussions : { listTool : 'shd_list_discussion_topics' , listArgs : { limit : 100 } , readTool : 'shd_list_discussion_messages' , detailKey : 'topic_id' , detailArgs : { limit : 50 } , paths : [ 'data' ] , sort : 'desc' } ,
notifications : { listTool : 'shd_list_notifications' , listArgs : { limit : 50 , offset : 0 } , paths : [ 'data' ] , sort : 'desc' } ,
scheduling : { listTool : 'shd_get_scheduling_overview' , listArgs : { limit : 100 } , readTool : 'shd_list_scheduling_bookings' , detailKey : 'booking_id' , detailArgs : { limit : 1 } , paths : [ 'data.bookings' , 'data.upcoming_bookings' , 'data' ] , sort : 'asc' } ,
inventory : { listTool : 'shd_list_inventory_items' , listArgs : { limit : 100 } , readTool : 'shd_get_inventory_item' , detailKey : 'item_id' , paths : [ 'data' ] , sort : 'desc' } ,
agents : { listTool : 'shd_agents_overview' , listArgs : { } , paths : [ 'data.controllers' , 'data.agents' , 'data.user_agents' , 'data' ] , sort : 'asc' } ,
'status-page' : { listTool : 'shd_get_status_page_monitors' , listArgs : { environment : 'dev' } , readTool : 'shd_get_status_page_monitor_history' , detailKey : 'monitor_id' , detailArgs : { limit : 20 } , paths : [ 'data' ] , nested : 'monitors' , sort : 'asc' } ,
'project-db' : { listTool : 'shd_project_db_tables' , listArgs : { } , readTool : 'shd_project_db_schema' , detailKey : 'table' , paths : [ 'data.tables' , 'data' ] , contextRequired : true , sort : 'asc' } ,
notes : { listTool : 'shd_list_note_pages' , listArgs : { limit : 100 } , readTool : 'shd_get_note_page' , detailKey : 'page_id' , paths : [ 'data' ] , sort : 'desc' }
} ;
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' ) , 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 : [ ] ) ; for ( var i = 0 ; i < ps . length ; i ++ ) { var c = path ( p , ps [ i ] ) , a = Array . isArray ( c ) ? c : findArray ( c , 0 ) ; if ( a ) return flatten ( a ) } 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 detailText ( x ) { return x === null || x === undefined || x === '' ? '—' : str ( x ) }
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 ] ) ) , ' ' + k ) ; e . summary . append ( n ) } ) }
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 } ) ; 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' , x . status || '—' , 'pill' ) ; if ( x . status ) s . classList . add ( String ( x . status ) . toLowerCase ( ) . replace ( /[^a-z0-9_-]+/g , '-' ) ) ; r . append ( n , make ( 'td' ) . appendChild ( s ) . parentNode , make ( 'td' , dateText ( x . date ) ) , make ( 'td' , x . 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 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 ) { var d = make ( 'div' , undefined , 'detail' ) ; d . append ( make ( 'span' , k , 'label' ) , make ( 'span' , detailText ( v ) , '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 ) ; if ( x . status ) add ( t ( 'status' ) , x . status ) ; if ( x . date ) add ( t ( 'date' ) , dateText ( x . date ) ) ; if ( x . value ) add ( t ( 'value' ) , x . 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 ( k , x [ 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 = merge ( state . config . sourceArgs , 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 ( q , d [ q ] ) } ) ; else add ( t ( 'details' ) , d ) } ) . catch ( function ( err ) { setStatus ( err . message || t ( 'detail' ) , true ) } ) }
function refresh ( ) { if ( state . busy ) return ; var a = merge ( state . config . listArgs , state . config . sourceArgs ) ; if ( state . config . contextRequired && ! a . base && ! a . project _code ) { 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 >
< / body >
< / html >