Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bceab8607c | |||
| 58abf98eea |
@@ -28,6 +28,7 @@ struct DebugSignalInfo {
|
|||||||
volatile bool isTracing;
|
volatile bool isTracing;
|
||||||
volatile bool isForcing;
|
volatile bool isForcing;
|
||||||
uint8 forcedValue[1024];
|
uint8 forcedValue[1024];
|
||||||
|
uint8 forcedMask[32]; // bit e set → element e is forced; supports up to 256 elements
|
||||||
uint32 internalID;
|
uint32 internalID;
|
||||||
volatile uint32 decimationFactor;
|
volatile uint32 decimationFactor;
|
||||||
volatile uint32 decimationCounter;
|
volatile uint32 decimationCounter;
|
||||||
|
|||||||
@@ -64,16 +64,34 @@ static void EscapeJson(const char8 *src, StreamString &dst) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool SuffixMatch(const char8 *target, const char8 *pattern) {
|
// Returns true if `str` ends with `suffix` at a word boundary (i.e. the char
|
||||||
uint32 tLen = StringHelper::Length(target);
|
// immediately before the match is '.', or the match covers the entire string).
|
||||||
uint32 pLen = StringHelper::Length(pattern);
|
static bool SuffixMatch(const char8 *str, const char8 *suffix) {
|
||||||
if (pLen > tLen)
|
uint32 sLen = StringHelper::Length(str);
|
||||||
|
uint32 sufLen = StringHelper::Length(suffix);
|
||||||
|
if (sufLen > sLen)
|
||||||
return false;
|
return false;
|
||||||
const char8 *suffix = target + (tLen - pLen);
|
const char8 *tail = str + (sLen - sufLen);
|
||||||
if (StringHelper::Compare(suffix, pattern) == 0) {
|
if (StringHelper::Compare(tail, suffix) != 0)
|
||||||
if (tLen == pLen || *(suffix - 1) == '.')
|
return false;
|
||||||
return true;
|
return (sLen == sufLen || *(tail - 1u) == '.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match a stored alias against a user-supplied name.
|
||||||
|
// Three cases are tried in order:
|
||||||
|
// 1. Exact match ("App.DS.Sig" == "App.DS.Sig")
|
||||||
|
// 2. Alias is longer (alias "App.DS.Sig" ends with user name "Sig")
|
||||||
|
// → user used a short / unqualified name in the command.
|
||||||
|
// 3. User name is longer (user name "App.GAM.In.Sig" ends with alias "GAM.In.Sig")
|
||||||
|
// → alias was registered with a short fallback path (FindByNameRecursive
|
||||||
|
// failed during broker init) but the client uses the full ORD path.
|
||||||
|
static bool AliasMatch(const char8 *aliasName, const char8 *userName) {
|
||||||
|
if (StringHelper::Compare(aliasName, userName) == 0)
|
||||||
|
return true;
|
||||||
|
if (SuffixMatch(aliasName, userName)) // case 2: alias ⊇ userName
|
||||||
|
return true;
|
||||||
|
if (SuffixMatch(userName, aliasName)) // case 3: userName ⊇ alias
|
||||||
|
return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +304,21 @@ void DebugServiceBase::ProcessSignal(DebugSignalInfo *signalInfo, uint32 size,
|
|||||||
if (signalInfo == NULL_PTR(DebugSignalInfo *))
|
if (signalInfo == NULL_PTR(DebugSignalInfo *))
|
||||||
return;
|
return;
|
||||||
if (signalInfo->isForcing) {
|
if (signalInfo->isForcing) {
|
||||||
memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size);
|
uint32 nEl = signalInfo->numberOfElements;
|
||||||
|
if (nEl <= 1u) {
|
||||||
|
// Scalar — single memcpy.
|
||||||
|
memcpy(signalInfo->memoryAddress, signalInfo->forcedValue, size);
|
||||||
|
} else {
|
||||||
|
// Array — copy only the elements whose bit is set in forcedMask.
|
||||||
|
uint32 elemBytes = size / nEl;
|
||||||
|
for (uint32 e = 0u; e < nEl; e++) {
|
||||||
|
if (signalInfo->forcedMask[e >> 3u] & (uint8)(1u << (e & 7u))) {
|
||||||
|
memcpy((uint8 *)signalInfo->memoryAddress + e * elemBytes,
|
||||||
|
signalInfo->forcedValue + e * elemBytes,
|
||||||
|
elemBytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (signalInfo->isTracing) {
|
if (signalInfo->isTracing) {
|
||||||
Atomic::Add((volatile int32 *)&signalInfo->decimationCounter, 1);
|
Atomic::Add((volatile int32 *)&signalInfo->decimationCounter, 1);
|
||||||
@@ -352,14 +384,39 @@ void DebugServiceBase::ConsumeStepIfNeeded(const char8 *gamName,
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) {
|
uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) {
|
||||||
|
// Parse optional array-element suffix: "SignalName[idx]" → baseName + elemIdx.
|
||||||
|
// elemIdx == -1 means "force all elements".
|
||||||
|
const char8 *bracketPos = StringHelper::SearchChar(name, '[');
|
||||||
|
StreamString baseName;
|
||||||
|
int32 elemIdx = -1;
|
||||||
|
|
||||||
|
if (bracketPos != NULL_PTR(const char8 *)) {
|
||||||
|
uint32 baseLen = (uint32)(bracketPos - name);
|
||||||
|
(void)baseName.Write(name, baseLen);
|
||||||
|
const char8 *closePos =
|
||||||
|
StringHelper::SearchChar(bracketPos + 1u, ']');
|
||||||
|
if (closePos != NULL_PTR(const char8 *)) {
|
||||||
|
uint32 idxLen = (uint32)(closePos - bracketPos - 1u);
|
||||||
|
StreamString idxStr;
|
||||||
|
(void)idxStr.Write(bracketPos + 1u, idxLen);
|
||||||
|
uint32 parsed = 0u;
|
||||||
|
AnyType iv(UnsignedInteger32Bit, 0u, &parsed);
|
||||||
|
AnyType is_src(CharString, 0u, idxStr.Buffer());
|
||||||
|
if (TypeConvert(iv, is_src))
|
||||||
|
elemIdx = (int32)parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseName = name;
|
||||||
|
}
|
||||||
|
const char8 *effectiveName = baseName.Buffer();
|
||||||
|
|
||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
uint32 count = 0u;
|
uint32 count = 0u;
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == name ||
|
if (AliasMatch(aliases[i].name.Buffer(), effectiveName)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), name)) {
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
||||||
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u;
|
||||||
uint32 elemBytes = (uint32)(s->type.numberOfBits) / 8u;
|
uint32 totalBytes = elemBytes * s->numberOfElements;
|
||||||
uint32 totalBytes = elemBytes * s->numberOfElements;
|
|
||||||
if (elemBytes == 0u || totalBytes > (uint32)sizeof(s->forcedValue)) {
|
if (elemBytes == 0u || totalBytes > (uint32)sizeof(s->forcedValue)) {
|
||||||
REPORT_ERROR_STATIC(
|
REPORT_ERROR_STATIC(
|
||||||
ErrorManagement::Warning,
|
ErrorManagement::Warning,
|
||||||
@@ -369,9 +426,27 @@ uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
s->isForcing = true;
|
s->isForcing = true;
|
||||||
AnyType dest(s->type, 0u, s->forcedValue);
|
|
||||||
AnyType source(CharString, 0u, valueStr);
|
if (elemIdx >= 0 && (uint32)elemIdx < s->numberOfElements) {
|
||||||
(void)TypeConvert(dest, source);
|
// Force a single element — write to its slot and set its mask bit.
|
||||||
|
uint8 *dest = (uint8 *)s->forcedValue + (uint32)elemIdx * elemBytes;
|
||||||
|
AnyType dv(s->type, 0u, (void *)dest);
|
||||||
|
AnyType sv(CharString, 0u, valueStr);
|
||||||
|
(void)TypeConvert(dv, sv);
|
||||||
|
s->forcedMask[(uint32)elemIdx >> 3u] |= (uint8)(1u << ((uint32)elemIdx & 7u));
|
||||||
|
} else {
|
||||||
|
// Force all elements to the same value — convert once, replicate, set all bits.
|
||||||
|
AnyType dv(s->type, 0u, s->forcedValue);
|
||||||
|
AnyType sv(CharString, 0u, valueStr);
|
||||||
|
(void)TypeConvert(dv, sv);
|
||||||
|
for (uint32 k = 1u; k < s->numberOfElements; k++) {
|
||||||
|
memcpy(s->forcedValue + k * elemBytes, s->forcedValue, elemBytes);
|
||||||
|
}
|
||||||
|
uint32 fullBytes = s->numberOfElements / 8u;
|
||||||
|
uint32 remBits = s->numberOfElements % 8u;
|
||||||
|
memset(s->forcedMask, 0xFF, fullBytes);
|
||||||
|
if (remBits > 0u) s->forcedMask[fullBytes] = (uint8)((1u << remBits) - 1u);
|
||||||
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,12 +456,48 @@ uint32 DebugServiceBase::ForceSignal(const char8 *name, const char8 *valueStr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32 DebugServiceBase::UnforceSignal(const char8 *name) {
|
uint32 DebugServiceBase::UnforceSignal(const char8 *name) {
|
||||||
|
// Parse optional "[idx]" suffix. If present, clear only that element's bit;
|
||||||
|
// if absent, clear all bits and the isForcing flag entirely.
|
||||||
|
const char8 *bracketPos = StringHelper::SearchChar(name, '[');
|
||||||
|
StreamString baseName;
|
||||||
|
int32 elemIdx = -1;
|
||||||
|
|
||||||
|
if (bracketPos != NULL_PTR(const char8 *)) {
|
||||||
|
uint32 baseLen = (uint32)(bracketPos - name);
|
||||||
|
(void)baseName.Write(name, baseLen);
|
||||||
|
const char8 *closePos = StringHelper::SearchChar(bracketPos + 1u, ']');
|
||||||
|
if (closePos != NULL_PTR(const char8 *)) {
|
||||||
|
uint32 idxLen = (uint32)(closePos - bracketPos - 1u);
|
||||||
|
StreamString idxStr;
|
||||||
|
(void)idxStr.Write(bracketPos + 1u, idxLen);
|
||||||
|
uint32 parsed = 0u;
|
||||||
|
AnyType iv(UnsignedInteger32Bit, 0u, &parsed);
|
||||||
|
AnyType is_src(CharString, 0u, idxStr.Buffer());
|
||||||
|
if (TypeConvert(iv, is_src)) elemIdx = (int32)parsed;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
baseName = name;
|
||||||
|
}
|
||||||
|
const char8 *effectiveName = baseName.Buffer();
|
||||||
|
|
||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
uint32 count = 0u;
|
uint32 count = 0u;
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == name ||
|
if (AliasMatch(aliases[i].name.Buffer(), effectiveName)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), name)) {
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
||||||
signals[aliases[i].signalIndex]->isForcing = false;
|
if (elemIdx >= 0 && (uint32)elemIdx < s->numberOfElements) {
|
||||||
|
// Clear just this element's force bit.
|
||||||
|
s->forcedMask[(uint32)elemIdx >> 3u] &= (uint8)(~(1u << ((uint32)elemIdx & 7u)));
|
||||||
|
// Deactivate forcing entirely if no bits remain.
|
||||||
|
uint32 numBytes = (s->numberOfElements + 7u) / 8u;
|
||||||
|
bool anySet = false;
|
||||||
|
for (uint32 b = 0u; b < numBytes && !anySet; b++) anySet = (s->forcedMask[b] != 0u);
|
||||||
|
if (!anySet) s->isForcing = false;
|
||||||
|
} else {
|
||||||
|
// Unforce all elements.
|
||||||
|
memset(s->forcedMask, 0, sizeof(s->forcedMask));
|
||||||
|
s->isForcing = false;
|
||||||
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,8 +511,7 @@ uint32 DebugServiceBase::TraceSignal(const char8 *name, bool enable,
|
|||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
uint32 count = 0u;
|
uint32 count = 0u;
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == name ||
|
if (AliasMatch(aliases[i].name.Buffer(), name)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
||||||
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
||||||
s->isTracing = enable;
|
s->isTracing = enable;
|
||||||
s->decimationFactor = decimation;
|
s->decimationFactor = decimation;
|
||||||
@@ -419,8 +529,7 @@ uint32 DebugServiceBase::SetBreak(const char8 *name, uint8 op,
|
|||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
uint32 count = 0u;
|
uint32 count = 0u;
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == name ||
|
if (AliasMatch(aliases[i].name.Buffer(), name)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
||||||
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
||||||
s->breakThreshold = threshold;
|
s->breakThreshold = threshold;
|
||||||
s->breakOp = op;
|
s->breakOp = op;
|
||||||
@@ -437,8 +546,7 @@ uint32 DebugServiceBase::ClearBreak(const char8 *name) {
|
|||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
uint32 count = 0u;
|
uint32 count = 0u;
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == name ||
|
if (AliasMatch(aliases[i].name.Buffer(), name)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
||||||
signals[aliases[i].signalIndex]->breakOp = BREAK_OFF;
|
signals[aliases[i].signalIndex]->breakOp = BREAK_OFF;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@@ -454,8 +562,7 @@ bool DebugServiceBase::IsInstrumented(const char8 *fullPath, bool &traceable,
|
|||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == fullPath ||
|
if (AliasMatch(aliases[i].name.Buffer(), fullPath)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), fullPath)) {
|
|
||||||
found = true;
|
found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -517,8 +624,7 @@ uint32 DebugServiceBase::RegisterMonitorSignal(const char8 *path,
|
|||||||
m.internalID = 0x80000000u | monitoredSignals.GetNumberOfElements();
|
m.internalID = 0x80000000u | monitoredSignals.GetNumberOfElements();
|
||||||
// Re-use existing brokered signal ID if available
|
// Re-use existing brokered signal ID if available
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == path ||
|
if (AliasMatch(aliases[i].name.Buffer(), path)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), path)) {
|
|
||||||
m.internalID = signals[aliases[i].signalIndex]->internalID;
|
m.internalID = signals[aliases[i].signalIndex]->internalID;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -652,8 +758,7 @@ void DebugServiceBase::GetSignalValue(const char8 *name, StreamString &out) {
|
|||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
DebugSignalInfo *sig = NULL_PTR(DebugSignalInfo *);
|
DebugSignalInfo *sig = NULL_PTR(DebugSignalInfo *);
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == name ||
|
if (AliasMatch(aliases[i].name.Buffer(), name)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), name)) {
|
|
||||||
sig = signals[aliases[i].signalIndex];
|
sig = signals[aliases[i].signalIndex];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -809,14 +914,156 @@ void DebugServiceBase::BuildDiscoverCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DebugServiceBase::BuildTreeCache() {
|
void DebugServiceBase::BuildTreeCache() {
|
||||||
treeCacheValid = false;
|
// No-op: tree is now served per-node via ExportTreeNode.
|
||||||
treeCache = "";
|
}
|
||||||
treeCache += "{\"Name\":\"Root\",\"Class\":\"ObjectRegistryDatabase\","
|
|
||||||
"\"Children\":[\n";
|
// ---------------------------------------------------------------------------
|
||||||
(void)ExportTree(ObjectRegistryDatabase::Instance(), treeCache,
|
// ExportTreeNode — serve one level of the object tree
|
||||||
NULL_PTR(const char8 *));
|
// ---------------------------------------------------------------------------
|
||||||
treeCache += "\n]}\nOK TREE\n";
|
|
||||||
treeCacheValid = true;
|
void DebugServiceBase::ExportTreeNode(const char8 *path, StreamString &out) {
|
||||||
|
bool isRoot =
|
||||||
|
(path == NULL_PTR(const char8 *) || StringHelper::Length(path) == 0);
|
||||||
|
|
||||||
|
out += "{\"Path\":\"";
|
||||||
|
EscapeJson(isRoot ? "" : path, out);
|
||||||
|
out += "\",\"Children\":[\n";
|
||||||
|
|
||||||
|
uint32 count = 0u;
|
||||||
|
|
||||||
|
ReferenceContainer *container = NULL_PTR(ReferenceContainer *);
|
||||||
|
DataSourceI * ds = NULL_PTR(DataSourceI *);
|
||||||
|
GAM * gam = NULL_PTR(GAM *);
|
||||||
|
|
||||||
|
if (isRoot) {
|
||||||
|
container = ObjectRegistryDatabase::Instance();
|
||||||
|
} else {
|
||||||
|
Reference ref = ObjectRegistryDatabase::Instance()->Find(path);
|
||||||
|
if (ref.IsValid()) {
|
||||||
|
container = dynamic_cast<ReferenceContainer *>(ref.operator->());
|
||||||
|
ds = dynamic_cast<DataSourceI *>(ref.operator->());
|
||||||
|
gam = dynamic_cast<GAM *>(ref.operator->());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReferenceContainer children
|
||||||
|
if (container != NULL_PTR(ReferenceContainer *)) {
|
||||||
|
uint32 n = container->Size();
|
||||||
|
for (uint32 i = 0u; i < n; i++) {
|
||||||
|
Reference child = container->Get(i);
|
||||||
|
if (!child.IsValid())
|
||||||
|
continue;
|
||||||
|
const char8 *cname = child->GetName();
|
||||||
|
if (cname == NULL_PTR(const char8 *))
|
||||||
|
cname = "unnamed";
|
||||||
|
|
||||||
|
ReferenceContainer *irc =
|
||||||
|
dynamic_cast<ReferenceContainer *>(child.operator->());
|
||||||
|
DataSourceI *ids = dynamic_cast<DataSourceI *>(child.operator->());
|
||||||
|
GAM * igam = dynamic_cast<GAM *>(child.operator->());
|
||||||
|
bool hasCh =
|
||||||
|
(irc != NULL_PTR(ReferenceContainer *) && irc->Size() > 0u) ||
|
||||||
|
ids != NULL_PTR(DataSourceI *) || igam != NULL_PTR(GAM *);
|
||||||
|
|
||||||
|
if (count > 0u)
|
||||||
|
out += ",\n";
|
||||||
|
out += "{\"Name\":\"";
|
||||||
|
EscapeJson(cname, out);
|
||||||
|
out += "\",\"Class\":\"";
|
||||||
|
EscapeJson(child->GetClassProperties()->GetName(), out);
|
||||||
|
out.Printf("\",\"HasChildren\":%s}", hasCh ? "true" : "false");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataSourceI signals
|
||||||
|
if (ds != NULL_PTR(DataSourceI *)) {
|
||||||
|
uint32 ns = ds->GetNumberOfSignals();
|
||||||
|
for (uint32 j = 0u; j < ns; j++) {
|
||||||
|
StreamString sn;
|
||||||
|
(void)ds->GetSignalName(j, sn);
|
||||||
|
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(
|
||||||
|
ds->GetSignalType(j));
|
||||||
|
uint8 d = 0u;
|
||||||
|
(void)ds->GetSignalNumberOfDimensions(j, d);
|
||||||
|
uint32 el = 0u;
|
||||||
|
(void)ds->GetSignalNumberOfElements(j, el);
|
||||||
|
StreamString sfp;
|
||||||
|
sfp.Printf("%s.%s", path, sn.Buffer());
|
||||||
|
bool tr = false, fo = false;
|
||||||
|
(void)IsInstrumented(sfp.Buffer(), tr, fo);
|
||||||
|
|
||||||
|
if (count > 0u)
|
||||||
|
out += ",\n";
|
||||||
|
out += "{\"Name\":\"";
|
||||||
|
EscapeJson(sn.Buffer(), out);
|
||||||
|
out += "\",\"Class\":\"Signal\",\"Type\":\"";
|
||||||
|
EscapeJson(st ? st : "Unknown", out);
|
||||||
|
out.Printf("\",\"Dimensions\":%u,\"Elements\":%u,"
|
||||||
|
"\"IsTraceable\":%s,\"IsForcable\":%s,\"HasChildren\":false}",
|
||||||
|
d, el, tr ? "true" : "false", fo ? "true" : "false");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GAM input signals
|
||||||
|
if (gam != NULL_PTR(GAM *)) {
|
||||||
|
uint32 nIn = gam->GetNumberOfInputSignals();
|
||||||
|
for (uint32 j = 0u; j < nIn; j++) {
|
||||||
|
StreamString sn;
|
||||||
|
(void)gam->GetSignalName(InputSignals, j, sn);
|
||||||
|
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(
|
||||||
|
gam->GetSignalType(InputSignals, j));
|
||||||
|
uint32 d = 0u;
|
||||||
|
(void)gam->GetSignalNumberOfDimensions(InputSignals, j, d);
|
||||||
|
uint32 el = 0u;
|
||||||
|
(void)gam->GetSignalNumberOfElements(InputSignals, j, el);
|
||||||
|
StreamString sfp;
|
||||||
|
sfp.Printf("%s.In.%s", path, sn.Buffer());
|
||||||
|
bool tr = false, fo = false;
|
||||||
|
(void)IsInstrumented(sfp.Buffer(), tr, fo);
|
||||||
|
|
||||||
|
if (count > 0u)
|
||||||
|
out += ",\n";
|
||||||
|
out += "{\"Name\":\"In.";
|
||||||
|
EscapeJson(sn.Buffer(), out);
|
||||||
|
out += "\",\"Class\":\"InputSignal\",\"Type\":\"";
|
||||||
|
EscapeJson(st ? st : "Unknown", out);
|
||||||
|
out.Printf("\",\"Dimensions\":%u,\"Elements\":%u,"
|
||||||
|
"\"IsTraceable\":%s,\"IsForcable\":%s,\"HasChildren\":false}",
|
||||||
|
d, el, tr ? "true" : "false", fo ? "true" : "false");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 nOut = gam->GetNumberOfOutputSignals();
|
||||||
|
for (uint32 j = 0u; j < nOut; j++) {
|
||||||
|
StreamString sn;
|
||||||
|
(void)gam->GetSignalName(OutputSignals, j, sn);
|
||||||
|
const char8 *st = TypeDescriptor::GetTypeNameFromTypeDescriptor(
|
||||||
|
gam->GetSignalType(OutputSignals, j));
|
||||||
|
uint32 d = 0u;
|
||||||
|
(void)gam->GetSignalNumberOfDimensions(OutputSignals, j, d);
|
||||||
|
uint32 el = 0u;
|
||||||
|
(void)gam->GetSignalNumberOfElements(OutputSignals, j, el);
|
||||||
|
StreamString sfp;
|
||||||
|
sfp.Printf("%s.Out.%s", path, sn.Buffer());
|
||||||
|
bool tr = false, fo = false;
|
||||||
|
(void)IsInstrumented(sfp.Buffer(), tr, fo);
|
||||||
|
|
||||||
|
if (count > 0u)
|
||||||
|
out += ",\n";
|
||||||
|
out += "{\"Name\":\"Out.";
|
||||||
|
EscapeJson(sn.Buffer(), out);
|
||||||
|
out += "\",\"Class\":\"OutputSignal\",\"Type\":\"";
|
||||||
|
EscapeJson(st ? st : "Unknown", out);
|
||||||
|
out.Printf("\",\"Dimensions\":%u,\"Elements\":%u,"
|
||||||
|
"\"IsTraceable\":%s,\"IsForcable\":%s,\"HasChildren\":false}",
|
||||||
|
d, el, tr ? "true" : "false", fo ? "true" : "false");
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out += "\n]}\nOK TREE\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) {
|
void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) {
|
||||||
@@ -857,8 +1104,7 @@ void DebugServiceBase::InfoNode(const char8 *path, StreamString &out) {
|
|||||||
mutex.FastLock();
|
mutex.FastLock();
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
for (uint32 i = 0u; i < aliases.GetNumberOfElements(); i++) {
|
||||||
if (aliases[i].name == path ||
|
if (AliasMatch(aliases[i].name.Buffer(), path)) {
|
||||||
SuffixMatch(aliases[i].name.Buffer(), path)) {
|
|
||||||
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
DebugSignalInfo *s = signals[aliases[i].signalIndex];
|
||||||
const char8 *tn =
|
const char8 *tn =
|
||||||
TypeDescriptor::GetTypeNameFromTypeDescriptor(s->type);
|
TypeDescriptor::GetTypeNameFromTypeDescriptor(s->type);
|
||||||
@@ -922,7 +1168,6 @@ void DebugServiceBase::SetFullConfig(ConfigurationDatabase &config) {
|
|||||||
config.Copy(fullConfig);
|
config.Copy(fullConfig);
|
||||||
manualConfigSet = true;
|
manualConfigSet = true;
|
||||||
BuildDiscoverCache();
|
BuildDiscoverCache();
|
||||||
BuildTreeCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DebugServiceBase::RebuildConfigFromRegistry() {
|
void DebugServiceBase::RebuildConfigFromRegistry() {
|
||||||
@@ -1289,9 +1534,10 @@ void DebugServiceBase::HandleCommand(const StreamString &cmdIn,
|
|||||||
} else if (token == "DISCOVER") {
|
} else if (token == "DISCOVER") {
|
||||||
Discover(out);
|
Discover(out);
|
||||||
} else if (token == "TREE") {
|
} else if (token == "TREE") {
|
||||||
if (!treeCacheValid)
|
StreamString path;
|
||||||
BuildTreeCache();
|
(void)cmd.GetToken(path, delims, term);
|
||||||
out += treeCache;
|
ExportTreeNode(path.Size() > 0u ? path.Buffer() : NULL_PTR(const char8 *),
|
||||||
|
out);
|
||||||
} else if (token == "INFO") {
|
} else if (token == "INFO") {
|
||||||
StreamString path;
|
StreamString path;
|
||||||
if (cmd.GetToken(path, delims, term))
|
if (cmd.GetToken(path, delims, term))
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ protected:
|
|||||||
|
|
||||||
uint32 ExportTree(ReferenceContainer *container, StreamString &json,
|
uint32 ExportTree(ReferenceContainer *container, StreamString &json,
|
||||||
const char8 *pathPrefix);
|
const char8 *pathPrefix);
|
||||||
|
void ExportTreeNode(const char8 *path, StreamString &out);
|
||||||
void EnrichWithConfig(const char8 *path, StreamString &json);
|
void EnrichWithConfig(const char8 *path, StreamString &json);
|
||||||
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
|
static void JsonifyDatabase(ConfigurationDatabase &db, StreamString &json);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -347,9 +347,11 @@ func (m *MarteClient) handleJSONResponse(tag, data string) {
|
|||||||
return
|
return
|
||||||
|
|
||||||
case "TREE":
|
case "TREE":
|
||||||
// Parse server-side and stream pre-digested flat-node batches to the
|
// Per-node response — forward to browser as-is.
|
||||||
// browser so it never has to JSON.parse a multi-MB string.
|
broadcast(m.hub, map[string]any{
|
||||||
go m.sendTreeBatches(data)
|
"type": "tree_node",
|
||||||
|
"data": data,
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
broadcast(m.hub, map[string]any{
|
broadcast(m.hub, map[string]any{
|
||||||
@@ -359,90 +361,6 @@ func (m *MarteClient) handleJSONResponse(tag, data string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// TREE streaming
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// TreeNode mirrors the DebugService TREE JSON structure.
|
|
||||||
type TreeNode struct {
|
|
||||||
Name string `json:"Name"`
|
|
||||||
Class string `json:"Class"`
|
|
||||||
IsTraceable bool `json:"IsTraceable"`
|
|
||||||
IsForcable bool `json:"IsForcable"`
|
|
||||||
Elements uint32 `json:"Elements"`
|
|
||||||
Children []*TreeNode `json:"Children"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlatNode is a pre-digested, compact representation sent to the browser.
|
|
||||||
type FlatNode struct {
|
|
||||||
Path string `json:"p"`
|
|
||||||
Name string `json:"n"`
|
|
||||||
Class string `json:"c"`
|
|
||||||
Parent string `json:"par"`
|
|
||||||
Tr bool `json:"tr,omitempty"`
|
|
||||||
Fo bool `json:"fo,omitempty"`
|
|
||||||
El uint32 `json:"el,omitempty"`
|
|
||||||
HasCh bool `json:"ch,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func flattenTree(node *TreeNode, parentPath string, out *[]FlatNode) {
|
|
||||||
path := node.Name
|
|
||||||
if parentPath != "" {
|
|
||||||
path = parentPath + "." + node.Name
|
|
||||||
}
|
|
||||||
hasCh := len(node.Children) > 0
|
|
||||||
*out = append(*out, FlatNode{
|
|
||||||
Path: path,
|
|
||||||
Name: node.Name,
|
|
||||||
Class: node.Class,
|
|
||||||
Parent: parentPath,
|
|
||||||
Tr: node.IsTraceable,
|
|
||||||
Fo: node.IsForcable,
|
|
||||||
El: node.Elements,
|
|
||||||
HasCh: hasCh,
|
|
||||||
})
|
|
||||||
for _, c := range node.Children {
|
|
||||||
flattenTree(c, path, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendTreeBatches parses the raw TREE JSON and streams compact FlatNode
|
|
||||||
// batches to all browsers. Each browser processes one batch per animation
|
|
||||||
// frame, keeping the UI responsive during large tree loads.
|
|
||||||
func (m *MarteClient) sendTreeBatches(raw string) {
|
|
||||||
var root TreeNode
|
|
||||||
if err := json.Unmarshal([]byte(raw), &root); err != nil {
|
|
||||||
log.Printf("[TREE] parse error: %v — falling back to raw JSON", err)
|
|
||||||
broadcast(m.hub, map[string]any{"type": "response", "tag": "TREE", "data": raw})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodes []FlatNode
|
|
||||||
if root.Name == "Root" && len(root.Children) > 0 {
|
|
||||||
for _, c := range root.Children {
|
|
||||||
flattenTree(c, "", &nodes)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
flattenTree(&root, "", &nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
total := len(nodes)
|
|
||||||
log.Printf("[TREE] streaming %d flat nodes to browser", total)
|
|
||||||
broadcast(m.hub, map[string]any{"type": "tree_clear", "total": total})
|
|
||||||
|
|
||||||
const batchSize = 50
|
|
||||||
for i := 0; i < total; i += batchSize {
|
|
||||||
end := i + batchSize
|
|
||||||
if end > total {
|
|
||||||
end = total
|
|
||||||
}
|
|
||||||
broadcast(m.hub, map[string]any{
|
|
||||||
"type": "tree_batch",
|
|
||||||
"nodes": nodes[i:end],
|
|
||||||
"done": end == total,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MarteClient) handleTextLine(line string) {
|
func (m *MarteClient) handleTextLine(line string) {
|
||||||
if strings.HasPrefix(line, "OK SERVICE_INFO") {
|
if strings.HasPrefix(line, "OK SERVICE_INFO") {
|
||||||
|
|||||||
+885
-193
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>MARTe2 Debug Client</title>
|
<title>MARTe2 Debug Client</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
<link rel="stylesheet" href="uplot.min.css">
|
<link rel="stylesheet" href="uplot.min.css">
|
||||||
<style>
|
<style>
|
||||||
*{box-sizing:border-box;margin:0;padding:0}
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
html,body{height:100%;overflow:hidden;font-family:'Segoe UI',system-ui,sans-serif;font-size:12px;background:#1e1e2e;color:#cdd6f4}
|
html,body{height:100%;overflow:hidden;font-family:'Segoe UI',system-ui,sans-serif;font-size:12px;background:#1e1e2e;color:#cdd6f4}
|
||||||
|
|
||||||
/* ── layout ── */
|
/* ── layout ── */
|
||||||
@@ -81,6 +81,7 @@ details>summary:hover{background:#313244}
|
|||||||
details>summary::before{content:'▶';font-size:9px;color:#585b70;width:10px;flex-shrink:0}
|
details>summary::before{content:'▶';font-size:9px;color:#585b70;width:10px;flex-shrink:0}
|
||||||
details[open]>summary::before{content:'▼'}
|
details[open]>summary::before{content:'▼'}
|
||||||
details>.children{padding-left:14px}
|
details>.children{padding-left:14px}
|
||||||
|
.tree-loading{padding:2px 4px;color:#585b70;font-size:10px;font-style:italic}
|
||||||
.tree-btn{background:transparent;border:1px solid #45475a;color:#a6adc8;padding:0 4px;border-radius:3px;cursor:pointer;font-size:10px;line-height:14px}
|
.tree-btn{background:transparent;border:1px solid #45475a;color:#a6adc8;padding:0 4px;border-radius:3px;cursor:pointer;font-size:10px;line-height:14px}
|
||||||
.tree-btn:hover{background:#45475a;color:#cdd6f4}
|
.tree-btn:hover{background:#45475a;color:#cdd6f4}
|
||||||
.tree-btn.t{border-color:#89b4fa;color:#89b4fa}
|
.tree-btn.t{border-color:#89b4fa;color:#89b4fa}
|
||||||
@@ -156,6 +157,21 @@ details>.children{padding-left:14px}
|
|||||||
::-webkit-scrollbar-thumb{background:#45475a;border-radius:3px}
|
::-webkit-scrollbar-thumb{background:#45475a;border-radius:3px}
|
||||||
::-webkit-scrollbar-thumb:hover{background:#585b70}
|
::-webkit-scrollbar-thumb:hover{background:#585b70}
|
||||||
|
|
||||||
|
/* ── grouped array signal in trace list ── */
|
||||||
|
.sig-group{display:flex;align-items:center;gap:4px;padding:3px 6px;border-bottom:1px solid #1e1e2e;user-select:none;cursor:grab}
|
||||||
|
.sig-group:hover{background:#313244}
|
||||||
|
.sig-group-exp{color:#585b70;font-size:10px;width:14px;text-align:center;flex-shrink:0;cursor:pointer}
|
||||||
|
.sig-group-exp:hover{color:#89b4fa}
|
||||||
|
.sig-item-sub{padding-left:24px;background:#11111b;border-left:2px solid #313244;margin-left:2px}
|
||||||
|
.sig-item-sub:hover{background:#1e1e2e}
|
||||||
|
|
||||||
|
/* ── array selection toggle ── */
|
||||||
|
.arr-seg{display:flex;gap:0;margin-bottom:12px;border-radius:4px;overflow:hidden;border:1px solid #45475a}
|
||||||
|
.arr-seg button{flex:1;border:none;border-radius:0;border-right:1px solid #45475a;color:#a6adc8;background:#313244;padding:4px 0;font-size:11px}
|
||||||
|
.arr-seg button:last-child{border-right:none}
|
||||||
|
.arr-seg button.active{background:#89b4fa;color:#1e1e2e}
|
||||||
|
.arr-seg button:hover:not(.active){background:#45475a;color:#cdd6f4}
|
||||||
|
|
||||||
/* ── misc ── */
|
/* ── misc ── */
|
||||||
.empty-hint{padding:16px;color:#45475a;text-align:center}
|
.empty-hint{padding:16px;color:#45475a;text-align:center}
|
||||||
.break-item{padding:3px 6px;border-bottom:1px solid #181825;display:flex;align-items:center;gap:4px;font-size:11px}
|
.break-item{padding:3px 6px;border-bottom:1px solid #181825;display:flex;align-items:center;gap:4px;font-size:11px}
|
||||||
@@ -163,250 +179,325 @@ details>.children{padding-left:14px}
|
|||||||
.msg-item{padding:3px 6px;border-bottom:1px solid #181825;font-size:11px}
|
.msg-item{padding:3px 6px;border-bottom:1px solid #181825;font-size:11px}
|
||||||
.msg-status{width:14px;text-align:center;flex-shrink:0}
|
.msg-status{width:14px;text-align:center;flex-shrink:0}
|
||||||
select{background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:2px 4px;border-radius:4px}
|
select{background:#313244;border:1px solid #45475a;color:#cdd6f4;padding:2px 4px;border-radius:4px}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
|
<!-- ═══ TOOLBAR ═══ -->
|
||||||
<!-- ═══ TOOLBAR ═══ -->
|
<div id="toolbar">
|
||||||
<div id="toolbar">
|
<!-- Connection menu -->
|
||||||
|
<div class="menu-wrap tb-group">
|
||||||
<!-- Connection menu -->
|
<button id="conn-menu-btn" onclick="toggleMenu('conn-dropdown')">
|
||||||
<div class="menu-wrap tb-group">
|
<span id="conn-status"></span>
|
||||||
<button id="conn-menu-btn" onclick="toggleMenu('conn-dropdown')">
|
Connection ▾
|
||||||
<span id="conn-status"></span> Connection ▾
|
</button>
|
||||||
</button>
|
<div class="dropdown" id="conn-dropdown">
|
||||||
<div class="dropdown" id="conn-dropdown">
|
<div class="menu-row">
|
||||||
<div class="menu-row">
|
<input type="text" id="host" value="127.0.0.1" placeholder="host" style="flex:2">
|
||||||
<input type="text" id="host" value="127.0.0.1" placeholder="host" style="flex:2">
|
<input type="number" id="port" value="8080" placeholder="ctrl" style="width:60px">
|
||||||
<input type="number" id="port" value="8080" placeholder="ctrl" style="width:60px">
|
<button id="btn-connect" onclick="toggleConnect()">Connect</button>
|
||||||
<button id="btn-connect" onclick="toggleConnect()">Connect</button>
|
</div>
|
||||||
|
<div class="menu-row" id="port-manual-row" style="display:none">
|
||||||
|
<label style="color:#a6adc8;font-size:11px;flex-shrink:0">UDP:</label>
|
||||||
|
<input type="number" id="udp-port" value="8081" placeholder="udp" style="width:72px">
|
||||||
|
<label style="color:#a6adc8;font-size:11px;flex-shrink:0">Log:</label>
|
||||||
|
<input type="number" id="log-port" value="8082" placeholder="log" style="width:72px">
|
||||||
|
</div>
|
||||||
|
<div class="menu-row">
|
||||||
|
<label class="form-check" style="margin:0;font-size:11px">
|
||||||
|
<input type="checkbox" id="auto-ports" checked onchange="toggleAutoPorts(this.checked)">
|
||||||
|
<span style="color:#a6adc8">Auto ports (SERVICE_INFO)</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<hr class="menu-sep">
|
||||||
|
<div class="menu-btn-row">
|
||||||
|
<button onclick="discoverCmd()">Discover</button>
|
||||||
|
<button onclick="treeCmd()">Tree</button>
|
||||||
|
<button onclick="serviceInfoCmd()">Info</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tb-group">
|
||||||
|
<button id="btn-pause" onclick="togglePause()">⏸ Pause</button>
|
||||||
|
<button onclick="openStepDialog()">⚙ Step</button>
|
||||||
|
</div>
|
||||||
|
<div class="tb-group">
|
||||||
|
<button onclick="addPlot()">+ Plot</button>
|
||||||
|
</div>
|
||||||
|
<div class="tb-group">
|
||||||
|
<button onclick="openForceDialog()">⚡ Force</button>
|
||||||
|
<button onclick="openBreakDialog()">🔴 Break</button>
|
||||||
|
<button onclick="openMsgDialog()">✉ Msg</button>
|
||||||
|
</div>
|
||||||
|
<div class="tb-group" style="border:none;margin-left:auto">
|
||||||
|
<span id="udp-stats" style="color:#585b70;font-size:11px">0 pkts</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-row" id="port-manual-row" style="display:none">
|
<!-- ═══ STEP STATUS BAR ═══ -->
|
||||||
<label style="color:#a6adc8;font-size:11px;flex-shrink:0">UDP:</label>
|
<div id="step-bar">
|
||||||
<input type="number" id="udp-port" value="8081" placeholder="udp" style="width:72px">
|
<span>⏹ PAUSED at
|
||||||
<label style="color:#a6adc8;font-size:11px;flex-shrink:0">Log:</label>
|
<b id="paused-gam">—</b></span>
|
||||||
<input type="number" id="log-port" value="8082" placeholder="log" style="width:72px">
|
<span id="step-remaining"></span>
|
||||||
|
<select id="step-thread" style="width:120px"></select>
|
||||||
|
<button onclick="step(1)">Step 1</button>
|
||||||
|
<button onclick="step(5)">Step 5</button>
|
||||||
|
<button onclick="step(20)">Step 20</button>
|
||||||
|
<button class="ok" onclick="togglePause()">▶ Resume</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-row">
|
<!-- ═══ MAIN ═══ -->
|
||||||
<label class="form-check" style="margin:0;font-size:11px">
|
<div id="main">
|
||||||
<input type="checkbox" id="auto-ports" checked onchange="toggleAutoPorts(this.checked)">
|
<!-- LEFT: object tree -->
|
||||||
<span style="color:#a6adc8">Auto ports (SERVICE_INFO)</span>
|
<div id="left-panel">
|
||||||
</label>
|
<div class="panel-header">
|
||||||
|
<span>Object Tree</span>
|
||||||
|
<button style="font-size:10px;padding:1px 6px" onclick="sendCmd('TREE')">↻</button>
|
||||||
|
</div>
|
||||||
|
<div class="panel-search"><input id="tree-search" oninput="filterTree(this.value)" placeholder="Search…"></div>
|
||||||
|
<div class="panel-body" id="tree-body">
|
||||||
|
<div class="empty-hint">Connect to MARTe2 then click Tree</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Left resize/collapse strip -->
|
||||||
|
<div id="left-strip" class="panel-strip" title="Drag to resize · Click to collapse"></div>
|
||||||
|
<!-- CENTER: plots -->
|
||||||
|
<div id="center-panel">
|
||||||
|
<div class="empty-hint" id="no-plots-hint">Add a plot panel to begin — drag signals from the tree or traced list</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right resize/collapse strip -->
|
||||||
|
<div id="right-strip" class="panel-strip" title="Drag to resize · Click to collapse"></div>
|
||||||
|
<!-- RIGHT: tabs -->
|
||||||
|
<div id="right-panel">
|
||||||
|
<div class="tabs">
|
||||||
|
<div class="tab active" onclick="switchTab('traced')">Traced</div>
|
||||||
|
<div class="tab" onclick="switchTab('forced')">Forced</div>
|
||||||
|
<div class="tab" onclick="switchTab('breaks')">Breaks</div>
|
||||||
|
<div class="tab" onclick="switchTab('msgs')">Msgs</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content active" id="tab-traced">
|
||||||
|
<div class="empty-hint" id="no-traced-hint">No signals traced</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" id="tab-forced">
|
||||||
|
<div class="empty-hint" id="no-forced-hint">No signals forced</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" id="tab-breaks">
|
||||||
|
<div class="empty-hint" id="no-breaks-hint">No breakpoints set</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-content" id="tab-msgs">
|
||||||
|
<div class="empty-hint" id="no-msgs-hint">No messages sent</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="menu-sep">
|
<!-- ═══ LOGS ═══ -->
|
||||||
<div class="menu-btn-row">
|
<div id="log-panel">
|
||||||
<button onclick="discoverCmd()">Discover</button>
|
<div id="log-toolbar">
|
||||||
<button onclick="treeCmd()">Tree</button>
|
<button class="panel-toggle" title="Collapse logs" onclick="togglePanel('log-panel','▼','▲',this)" id="log-toggle-btn">▼</button>
|
||||||
<button onclick="serviceInfoCmd()">Info</button>
|
<span style="font-weight:600;color:#89b4fa">Logs</span>
|
||||||
|
<label><input type="checkbox" id="lf-service" onchange="renderLogs()">
|
||||||
|
Service</label>
|
||||||
|
<label><input type="checkbox" id="lf-debug" checked onchange="renderLogs()">
|
||||||
|
Debug</label>
|
||||||
|
<label><input type="checkbox" id="lf-info" checked onchange="renderLogs()">
|
||||||
|
Info</label>
|
||||||
|
<label><input type="checkbox" id="lf-warn" checked onchange="renderLogs()">
|
||||||
|
Warn</label>
|
||||||
|
<label><input type="checkbox" id="lf-error" checked onchange="renderLogs()">
|
||||||
|
Error</label>
|
||||||
|
<input type="text" id="log-filter" placeholder="Filter…" oninput="renderLogs()" style="width:150px">
|
||||||
|
<button onclick="logs=[];renderLogs()" style="margin-left:auto">Clear</button>
|
||||||
|
<label><input type="checkbox" id="log-autoscroll" checked>
|
||||||
|
Auto-scroll</label>
|
||||||
|
</div>
|
||||||
|
<div id="log-body"></div>
|
||||||
|
</div>
|
||||||
|
</div><!-- #app -->
|
||||||
|
<!-- ═══ DIALOGS ═══ -->
|
||||||
|
<!-- Force dialog -->
|
||||||
|
<div class="dialog-overlay" id="dlg-force" style="display:none" onclick="if(event.target===this)closeDlg('dlg-force')">
|
||||||
|
<div class="dialog">
|
||||||
|
<h3>⚡ Force Signal</h3>
|
||||||
|
<label>Signal</label>
|
||||||
|
<input id="force-sig" list="force-sig-list" placeholder="Signal path…">
|
||||||
|
<datalist id="force-sig-list"></datalist>
|
||||||
|
<label>Value</label>
|
||||||
|
<input id="force-val" placeholder="e.g. 42">
|
||||||
|
<div class="btns">
|
||||||
|
<button onclick="closeDlg('dlg-force')">Cancel</button>
|
||||||
|
<button class="active" onclick="doForce()">Force</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- Unforce confirm -->
|
||||||
|
<div class="dialog-overlay" id="dlg-unforce" style="display:none" onclick="if(event.target===this)closeDlg('dlg-unforce')">
|
||||||
<div class="tb-group">
|
<div class="dialog">
|
||||||
<button id="btn-pause" onclick="togglePause()">⏸ Pause</button>
|
<h3>Remove Force</h3>
|
||||||
<button onclick="openStepDialog()">⚙ Step</button>
|
<p id="unforce-msg" style="margin-bottom:12px;color:#cdd6f4"></p>
|
||||||
</div>
|
<div class="btns">
|
||||||
<div class="tb-group">
|
<button onclick="closeDlg('dlg-unforce')">Cancel</button>
|
||||||
<button onclick="addPlot()">+ Plot</button>
|
<button class="danger" onclick="doUnforce()">Unforce</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="tb-group">
|
</div>
|
||||||
<button onclick="openForceDialog()">⚡ Force</button>
|
|
||||||
<button onclick="openBreakDialog()">🔴 Break</button>
|
|
||||||
<button onclick="openMsgDialog()">✉ Msg</button>
|
|
||||||
</div>
|
|
||||||
<div class="tb-group" style="border:none;margin-left:auto">
|
|
||||||
<span id="udp-stats" style="color:#585b70;font-size:11px">0 pkts</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ STEP STATUS BAR ═══ -->
|
|
||||||
<div id="step-bar">
|
|
||||||
<span>⏹ PAUSED at <b id="paused-gam">—</b></span>
|
|
||||||
<span id="step-remaining"></span>
|
|
||||||
<select id="step-thread" style="width:120px"></select>
|
|
||||||
<button onclick="step(1)">Step 1</button>
|
|
||||||
<button onclick="step(5)">Step 5</button>
|
|
||||||
<button onclick="step(20)">Step 20</button>
|
|
||||||
<button class="ok" onclick="togglePause()">▶ Resume</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ═══ MAIN ═══ -->
|
|
||||||
<div id="main">
|
|
||||||
|
|
||||||
<!-- LEFT: object tree -->
|
|
||||||
<div id="left-panel">
|
|
||||||
<div class="panel-header">
|
|
||||||
<span>Object Tree</span>
|
|
||||||
<button style="font-size:10px;padding:1px 6px" onclick="sendCmd('TREE')">↻</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-search"><input id="tree-search" oninput="filterTree(this.value)" placeholder="Search…"></div>
|
<!-- Break dialog -->
|
||||||
<div class="panel-body" id="tree-body">
|
<div class="dialog-overlay" id="dlg-break" style="display:none" onclick="if(event.target===this)closeDlg('dlg-break')">
|
||||||
<div class="empty-hint">Connect to MARTe2 then click Tree</div>
|
<div class="dialog">
|
||||||
|
<h3>🔴 Set Breakpoint</h3>
|
||||||
|
<label>Signal</label>
|
||||||
|
<input id="break-sig" list="break-sig-list" placeholder="Signal path…">
|
||||||
|
<datalist id="break-sig-list"></datalist>
|
||||||
|
<div class="form-row">
|
||||||
|
<div>
|
||||||
|
<label>Operator</label>
|
||||||
|
<select id="break-op">
|
||||||
|
<option>></option><option>>=</option>
|
||||||
|
<option><</option><option><=</option>
|
||||||
|
<option>==</option><option>!=</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Threshold</label>
|
||||||
|
<input id="break-thresh" placeholder="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<button onclick="closeDlg('dlg-break')">Cancel</button>
|
||||||
|
<button class="active" onclick="doBreak()">Set Break</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- Message dialog -->
|
||||||
|
<div class="dialog-overlay" id="dlg-msg" style="display:none" onclick="if(event.target===this)closeDlg('dlg-msg')">
|
||||||
<!-- Left resize/collapse strip -->
|
<div class="dialog">
|
||||||
<div id="left-strip" class="panel-strip" title="Drag to resize · Click to collapse"></div>
|
<h3>✉ Send Message</h3>
|
||||||
|
<label>Destination</label>
|
||||||
<!-- CENTER: plots -->
|
<input id="msg-dest" list="msg-dest-list" placeholder="e.g. App.Functions.GAM1">
|
||||||
<div id="center-panel">
|
<datalist id="msg-dest-list"></datalist>
|
||||||
<div class="empty-hint" id="no-plots-hint">Add a plot panel to begin — drag signals from the tree or traced list</div>
|
<label>Function</label>
|
||||||
</div>
|
<input id="msg-func" placeholder="FunctionName">
|
||||||
|
<label>Payload (key=value lines)</label>
|
||||||
<!-- Right resize/collapse strip -->
|
<textarea id="msg-payload" placeholder="Key = Value Key2 = Value2"></textarea>
|
||||||
<div id="right-strip" class="panel-strip" title="Drag to resize · Click to collapse"></div>
|
<div class="form-check">
|
||||||
|
<input type="checkbox" id="msg-wait">
|
||||||
<!-- RIGHT: tabs -->
|
<label for="msg-wait">Wait for reply</label>
|
||||||
<div id="right-panel">
|
</div>
|
||||||
<div class="tabs">
|
<div class="btns">
|
||||||
<div class="tab active" onclick="switchTab('traced')">Traced</div>
|
<button onclick="closeDlg('dlg-msg')">Cancel</button>
|
||||||
<div class="tab" onclick="switchTab('forced')">Forced</div>
|
<button class="active" onclick="doSendMsg()">Send</button>
|
||||||
<div class="tab" onclick="switchTab('breaks')">Breaks</div>
|
</div>
|
||||||
<div class="tab" onclick="switchTab('msgs')">Msgs</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content active" id="tab-traced">
|
<!-- Array signal dialog (Trace / Force / Break with index/range selection) -->
|
||||||
<div class="empty-hint" id="no-traced-hint">No signals traced</div>
|
<div class="dialog-overlay" id="dlg-array" style="display:none" onclick="if(event.target===this)closeDlg('dlg-array')">
|
||||||
|
<div class="dialog" style="min-width:360px">
|
||||||
|
<h3 id="arr-dlg-title">Array Signal</h3>
|
||||||
|
<p style="font-size:11px;color:#a6adc8;margin-bottom:14px">
|
||||||
|
<b id="arr-dlg-sig" style="color:#cdd6f4"></b>
|
||||||
|
· <span id="arr-dlg-n" style="color:#89b4fa"></span> elements
|
||||||
|
</p>
|
||||||
|
<!-- Segmented selection toggle -->
|
||||||
|
<label style="margin-bottom:6px">Apply to</label>
|
||||||
|
<div class="arr-seg">
|
||||||
|
<button id="arr-sel-all" class="active" onclick="setArrSel('all')">All</button>
|
||||||
|
<button id="arr-sel-idx" onclick="setArrSel('idx')">Index</button>
|
||||||
|
<button id="arr-sel-rng" onclick="setArrSel('rng')">Range</button>
|
||||||
|
</div>
|
||||||
|
<!-- Index mode: single number input -->
|
||||||
|
<div id="arr-idx-sect" style="display:none">
|
||||||
|
<label>Element index <span style="color:#585b70;font-weight:normal">(0 – <span id="arr-idx-max"></span>)</span></label>
|
||||||
|
<input id="arr-idx" type="number" min="0" value="0">
|
||||||
|
</div>
|
||||||
|
<!-- Range mode: start / end -->
|
||||||
|
<div id="arr-rng-sect" style="display:none">
|
||||||
|
<div class="form-row">
|
||||||
|
<div>
|
||||||
|
<label>From index</label>
|
||||||
|
<input id="arr-r0" type="number" min="0" value="0">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>To index <span style="color:#585b70;font-weight:normal">inclusive</span></label>
|
||||||
|
<input id="arr-r1" type="number" min="0" value="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Force value (shown only for Force action) -->
|
||||||
|
<div id="arr-force-sect" style="display:none">
|
||||||
|
<label>Force value</label>
|
||||||
|
<input id="arr-force-val" placeholder="e.g. 0">
|
||||||
|
</div>
|
||||||
|
<!-- Break condition (shown only for Break action) -->
|
||||||
|
<div id="arr-break-sect" style="display:none">
|
||||||
|
<div class="form-row">
|
||||||
|
<div>
|
||||||
|
<label>Condition</label>
|
||||||
|
<select id="arr-break-op">
|
||||||
|
<option>></option><option>>=</option>
|
||||||
|
<option><</option><option><=</option>
|
||||||
|
<option>==</option><option>!=</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Threshold</label>
|
||||||
|
<input id="arr-break-thr" placeholder="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<button onclick="closeDlg('dlg-array')">Cancel</button>
|
||||||
|
<button id="arr-ok-btn" class="active" onclick="doArrayOp()">Apply</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" id="tab-forced">
|
<!-- Array → plot mode dialog (Sequential / Waterfall) -->
|
||||||
<div class="empty-hint" id="no-forced-hint">No signals forced</div>
|
<div class="dialog-overlay" id="dlg-arr-plot" style="display:none" onclick="if(event.target===this)closeDlg('dlg-arr-plot')">
|
||||||
|
<div class="dialog" style="min-width:360px">
|
||||||
|
<h3>Plot Array Signal</h3>
|
||||||
|
<p style="font-size:11px;color:#a6adc8;margin-bottom:14px">
|
||||||
|
<b id="arrp-name" style="color:#cdd6f4"></b>
|
||||||
|
· <span id="arrp-n" style="color:#89b4fa"></span>
|
||||||
|
elements
|
||||||
|
</p>
|
||||||
|
<div class="arr-seg" style="margin-bottom:8px">
|
||||||
|
<button id="arrp-sel-sequential" class="active" onclick="setArrpMode('sequential')">Sequential</button>
|
||||||
|
<button id="arrp-sel-waterfall" onclick="setArrpMode('waterfall')">Waterfall</button>
|
||||||
|
</div>
|
||||||
|
<p id="arrp-desc" style="font-size:10px;color:#a6adc8;margin-bottom:16px;min-height:28px"></p>
|
||||||
|
<div class="btns">
|
||||||
|
<button onclick="closeDlg('dlg-arr-plot')">Cancel</button>
|
||||||
|
<button class="active" onclick="doArrayPlotMode()">Add</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" id="tab-breaks">
|
<!-- Info dialog -->
|
||||||
<div class="empty-hint" id="no-breaks-hint">No breakpoints set</div>
|
<div class="dialog-overlay" id="dlg-info" style="display:none" onclick="if(event.target===this)closeDlg('dlg-info')">
|
||||||
|
<div class="dialog" style="max-width:600px;width:90vw">
|
||||||
|
<h3 id="info-title">Info</h3>
|
||||||
|
<pre id="info-body" style="background:#11111b;padding:8px;border-radius:4px;overflow:auto;max-height:400px;font-size:11px;color:#cdd6f4;white-space:pre-wrap"></pre>
|
||||||
|
<div class="btns" style="margin-top:12px">
|
||||||
|
<button onclick="closeDlg('dlg-info')">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-content" id="tab-msgs">
|
<!-- Step dialog -->
|
||||||
<div class="empty-hint" id="no-msgs-hint">No messages sent</div>
|
<div class="dialog-overlay" id="dlg-step" style="display:none" onclick="if(event.target===this)closeDlg('dlg-step')">
|
||||||
|
<div class="dialog">
|
||||||
|
<h3>⚙ Step Execution</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div>
|
||||||
|
<label>Count</label>
|
||||||
|
<input type="number" id="step-count" value="1" min="1">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Thread (optional)</label>
|
||||||
|
<input id="step-thread-inp" list="step-thread-list" placeholder="all">
|
||||||
|
<datalist id="step-thread-list"></datalist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="btns">
|
||||||
|
<button onclick="closeDlg('dlg-step')">Cancel</button>
|
||||||
|
<button class="active" onclick="doStep()">Step</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<script src="uplot.min.js"></script>
|
||||||
|
<script src="app.js"></script>
|
||||||
</div>
|
</body>
|
||||||
|
</html>
|
||||||
<!-- ═══ LOGS ═══ -->
|
|
||||||
<div id="log-panel">
|
|
||||||
<div id="log-toolbar">
|
|
||||||
<button class="panel-toggle" title="Collapse logs" onclick="togglePanel('log-panel','▼','▲',this)" id="log-toggle-btn">▼</button>
|
|
||||||
<span style="font-weight:600;color:#89b4fa">Logs</span>
|
|
||||||
<label><input type="checkbox" id="lf-debug" checked onchange="renderLogs()"> Debug</label>
|
|
||||||
<label><input type="checkbox" id="lf-info" checked onchange="renderLogs()"> Info</label>
|
|
||||||
<label><input type="checkbox" id="lf-warn" checked onchange="renderLogs()"> Warn</label>
|
|
||||||
<label><input type="checkbox" id="lf-error" checked onchange="renderLogs()"> Error</label>
|
|
||||||
<input type="text" id="log-filter" placeholder="Filter…" oninput="renderLogs()" style="width:150px">
|
|
||||||
<button onclick="logs=[];renderLogs()" style="margin-left:auto">Clear</button>
|
|
||||||
<label><input type="checkbox" id="log-autoscroll" checked> Auto-scroll</label>
|
|
||||||
</div>
|
|
||||||
<div id="log-body"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div><!-- #app -->
|
|
||||||
|
|
||||||
<!-- ═══ DIALOGS ═══ -->
|
|
||||||
|
|
||||||
<!-- Force dialog -->
|
|
||||||
<div class="dialog-overlay" id="dlg-force" style="display:none" onclick="if(event.target===this)closeDlg('dlg-force')">
|
|
||||||
<div class="dialog">
|
|
||||||
<h3>⚡ Force Signal</h3>
|
|
||||||
<label>Signal</label>
|
|
||||||
<input id="force-sig" list="force-sig-list" placeholder="Signal path…">
|
|
||||||
<datalist id="force-sig-list"></datalist>
|
|
||||||
<label>Value</label>
|
|
||||||
<input id="force-val" placeholder="e.g. 42">
|
|
||||||
<div class="btns">
|
|
||||||
<button onclick="closeDlg('dlg-force')">Cancel</button>
|
|
||||||
<button class="active" onclick="doForce()">Force</button>
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
|
|
||||||
<!-- Unforce confirm -->
|
|
||||||
<div class="dialog-overlay" id="dlg-unforce" style="display:none" onclick="if(event.target===this)closeDlg('dlg-unforce')">
|
|
||||||
<div class="dialog">
|
|
||||||
<h3>Remove Force</h3>
|
|
||||||
<p id="unforce-msg" style="margin-bottom:12px;color:#cdd6f4"></p>
|
|
||||||
<div class="btns">
|
|
||||||
<button onclick="closeDlg('dlg-unforce')">Cancel</button>
|
|
||||||
<button class="danger" onclick="doUnforce()">Unforce</button>
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
|
|
||||||
<!-- Break dialog -->
|
|
||||||
<div class="dialog-overlay" id="dlg-break" style="display:none" onclick="if(event.target===this)closeDlg('dlg-break')">
|
|
||||||
<div class="dialog">
|
|
||||||
<h3>🔴 Set Breakpoint</h3>
|
|
||||||
<label>Signal</label>
|
|
||||||
<input id="break-sig" list="break-sig-list" placeholder="Signal path…">
|
|
||||||
<datalist id="break-sig-list"></datalist>
|
|
||||||
<div class="form-row">
|
|
||||||
<div>
|
|
||||||
<label>Operator</label>
|
|
||||||
<select id="break-op">
|
|
||||||
<option>></option><option>>=</option>
|
|
||||||
<option><</option><option><=</option>
|
|
||||||
<option>==</option><option>!=</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Threshold</label>
|
|
||||||
<input id="break-thresh" placeholder="0">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="btns">
|
|
||||||
<button onclick="closeDlg('dlg-break')">Cancel</button>
|
|
||||||
<button class="active" onclick="doBreak()">Set Break</button>
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
|
|
||||||
<!-- Message dialog -->
|
|
||||||
<div class="dialog-overlay" id="dlg-msg" style="display:none" onclick="if(event.target===this)closeDlg('dlg-msg')">
|
|
||||||
<div class="dialog">
|
|
||||||
<h3>✉ Send Message</h3>
|
|
||||||
<label>Destination</label>
|
|
||||||
<input id="msg-dest" list="msg-dest-list" placeholder="e.g. App.Functions.GAM1">
|
|
||||||
<datalist id="msg-dest-list"></datalist>
|
|
||||||
<label>Function</label>
|
|
||||||
<input id="msg-func" placeholder="FunctionName">
|
|
||||||
<label>Payload (key=value lines)</label>
|
|
||||||
<textarea id="msg-payload" placeholder="Key = Value Key2 = Value2"></textarea>
|
|
||||||
<div class="form-check">
|
|
||||||
<input type="checkbox" id="msg-wait">
|
|
||||||
<label for="msg-wait">Wait for reply</label>
|
|
||||||
</div>
|
|
||||||
<div class="btns">
|
|
||||||
<button onclick="closeDlg('dlg-msg')">Cancel</button>
|
|
||||||
<button class="active" onclick="doSendMsg()">Send</button>
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
|
|
||||||
<!-- Info dialog -->
|
|
||||||
<div class="dialog-overlay" id="dlg-info" style="display:none" onclick="if(event.target===this)closeDlg('dlg-info')">
|
|
||||||
<div class="dialog" style="max-width:600px;width:90vw">
|
|
||||||
<h3 id="info-title">Info</h3>
|
|
||||||
<pre id="info-body" style="background:#11111b;padding:8px;border-radius:4px;overflow:auto;max-height:400px;font-size:11px;color:#cdd6f4;white-space:pre-wrap"></pre>
|
|
||||||
<div class="btns" style="margin-top:12px">
|
|
||||||
<button onclick="closeDlg('dlg-info')">Close</button>
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
|
|
||||||
<!-- Step dialog -->
|
|
||||||
<div class="dialog-overlay" id="dlg-step" style="display:none" onclick="if(event.target===this)closeDlg('dlg-step')">
|
|
||||||
<div class="dialog">
|
|
||||||
<h3>⚙ Step Execution</h3>
|
|
||||||
<div class="form-row">
|
|
||||||
<div>
|
|
||||||
<label>Count</label>
|
|
||||||
<input type="number" id="step-count" value="1" min="1">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label>Thread (optional)</label>
|
|
||||||
<input id="step-thread-inp" list="step-thread-list" placeholder="all">
|
|
||||||
<datalist id="step-thread-list"></datalist>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="btns">
|
|
||||||
<button onclick="closeDlg('dlg-step')">Cancel</button>
|
|
||||||
<button class="active" onclick="doStep()">Step</button>
|
|
||||||
</div>
|
|
||||||
</div></div>
|
|
||||||
|
|
||||||
<script src="uplot.min.js"></script>
|
|
||||||
<script src="app.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user