feat(scripts): realign Test Run with runtime API, add anonymous-object calls and instance binding
The Test Run sandbox and Monaco analysis modelled a script API that had drifted from the site runtime's ScriptGlobals, so real scripts failed to compile in Test Run. Realign both to the runtime surface (Instance/Scripts/ExternalSystem/Attributes/Children/Parent) and drop the duplicate ScriptHost stub so the two cannot diverge again. - Script calls (Scripts.CallShared, Instance.CallScript, Route.To().Call) accept an anonymous object instead of a hand-built dictionary, via a shared ScriptArgs normalizer; existing dictionary calls still compile. - Test Run can optionally bind to a deployed instance, so Instance/ Attributes/CallScript route to it cross-site; adds site-side RouteToGetAttributes/RouteToSetAttributes handlers. - Adds Test Run panels to the API method and template script editors. - Fixes the TestDatabaseQuery seed script, which queried a table that never existed. Also commits unrelated in-progress work already in the tree: the health monitoring report loop, site streaming changes, and the Admin/Design data-connection and SMTP page reorganization.
This commit is contained in:
@@ -14,8 +14,6 @@
|
||||
<div class="container-fluid mt-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h4 class="mb-0">Integration Definitions</h4>
|
||||
<a class="btn btn-outline-secondary btn-sm"
|
||||
href="/design/smtp">Email configuration →</a>
|
||||
</div>
|
||||
|
||||
<ToastNotification @ref="_toast" />
|
||||
@@ -67,15 +65,6 @@
|
||||
Inbound API Methods <span class="badge bg-secondary">@_apiMethods.Count</span>
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link @(_tab == "apikeys" ? "active" : "")"
|
||||
role="tab"
|
||||
aria-selected="@(_tab == "apikeys" ? "true" : "false")"
|
||||
aria-controls="int-tab-apikeys"
|
||||
@onclick='() => _tab = "apikeys"'>
|
||||
API Keys <span class="badge bg-secondary">@_apiKeys.Count</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@if (_tab == "extsys")
|
||||
@@ -94,10 +83,6 @@
|
||||
{
|
||||
<div role="tabpanel" id="int-tab-inbound">@RenderInboundApiMethods()</div>
|
||||
}
|
||||
else if (_tab == "apikeys")
|
||||
{
|
||||
<div role="tabpanel" id="int-tab-apikeys">@RenderApiKeys()</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -122,14 +107,6 @@
|
||||
? _dbConnections
|
||||
: _dbConnections.Where(dc => dc.Name?.Contains(_dbConnSearch, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
|
||||
// API Keys
|
||||
private List<ApiKey> _apiKeys = new();
|
||||
private string _apiKeySearch = "";
|
||||
private IEnumerable<ApiKey> FilteredApiKeys =>
|
||||
string.IsNullOrWhiteSpace(_apiKeySearch)
|
||||
? _apiKeys
|
||||
: _apiKeys.Where(k => k.Name?.Contains(_apiKeySearch, StringComparison.OrdinalIgnoreCase) ?? false);
|
||||
|
||||
// Notification Lists
|
||||
private List<NotificationList> _notificationLists = new();
|
||||
private Dictionary<int, List<NotificationRecipient>> _recipients = new();
|
||||
@@ -171,7 +148,6 @@
|
||||
}
|
||||
|
||||
_apiMethods = (await InboundApiRepository.GetAllApiMethodsAsync()).ToList();
|
||||
_apiKeys = (await InboundApiRepository.GetAllApiKeysAsync()).ToList();
|
||||
}
|
||||
catch (Exception ex) { _errorMessage = ex.Message; }
|
||||
_loading = false;
|
||||
@@ -478,67 +454,4 @@
|
||||
catch (Exception ex) { _toast.ShowError(ex.Message); }
|
||||
}
|
||||
|
||||
// ==== API Keys ====
|
||||
private RenderFragment RenderApiKeys() => __builder =>
|
||||
{
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<h5 class="mb-0">API Keys</h5>
|
||||
</div>
|
||||
|
||||
@if (_apiKeys.Count == 0)
|
||||
{
|
||||
<div class="text-center py-5 text-muted">
|
||||
<p class="mb-3">No API keys configured. Add your first API key from the Admin section.</p>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mb-3" style="max-width: 320px;">
|
||||
<input class="form-control form-control-sm"
|
||||
placeholder="Filter by name…"
|
||||
@bind="_apiKeySearch" @bind:event="oninput" />
|
||||
</div>
|
||||
|
||||
@if (!FilteredApiKeys.Any())
|
||||
{
|
||||
<p class="text-muted small">No API keys match the filter.</p>
|
||||
}
|
||||
|
||||
<div class="row g-3">
|
||||
@foreach (var key in FilteredApiKeys)
|
||||
{
|
||||
<div class="col-lg-6 col-12" @key="key.Id">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-0">@key.Name</h5>
|
||||
<span class="badge @(key.IsEnabled ? "bg-success" : "bg-secondary")">
|
||||
@(key.IsEnabled ? "Enabled" : "Disabled")
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex gap-1">
|
||||
<button class="btn btn-outline-primary btn-sm"
|
||||
@onclick="() => ToggleApiKeyEnabled(key)">
|
||||
@(key.IsEnabled ? "Disable" : "Enable")
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
};
|
||||
|
||||
private async Task ToggleApiKeyEnabled(ApiKey key)
|
||||
{
|
||||
try
|
||||
{
|
||||
key.IsEnabled = !key.IsEnabled;
|
||||
await InboundApiRepository.UpdateApiKeyAsync(key);
|
||||
await InboundApiRepository.SaveChangesAsync();
|
||||
_toast.ShowSuccess($"API key '{key.Name}' {(key.IsEnabled ? "enabled" : "disabled")}.");
|
||||
}
|
||||
catch (Exception ex) { _toast.ShowError(ex.Message); }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user