Commit Graph

8 Commits

Author SHA1 Message Date
Joseph Doherty 1c2dc45803 feat(ui/api-methods): pick approved API keys when editing a method
The ApiMethod entity had an ApprovedApiKeyIds column and ApiKeyValidator
read it, but no UI/CLI/seed code ever wrote to it. Result: any inbound
POST /api/{method} was rejected with 403 "API key not approved for this
method" regardless of which key was sent.

Add an "Approved API Keys" subsection to the method form, between
Timeout and Parameters: vertical list of checkboxes, one per ApiKey
row (with a "Disabled" badge for disabled keys, and a link to
/admin/api-keys when none exist). OnInitializedAsync loads all keys and
parses the existing comma-separated IDs; Save() serializes the selected
set back to the entity on both create and edit paths.

Re-uses IInboundApiRepository.GetAllApiKeysAsync — no repo or migration
changes needed.
2026-05-13 07:12:44 -04:00
Joseph Doherty 783da8e21a feat(ui): structured editors for script schemas and alarm triggers
Replace raw-JSON text inputs with rich UI: script parameter/return types use
a JSON Schema builder (SchemaBuilder + JsonSchemaShapeParser, with a migration
to convert existing definitions); alarm trigger config uses a type-aware
editor with a flattened attribute picker (AlarmTriggerEditor). AlarmActor
gains optional direction (rising/falling/either) on RateOfChange triggers.
2026-05-13 00:33:00 -04:00
Joseph Doherty 0528c65cba feat(ui/scripts): format, inlay hints, problems panel, type diagnostic
Three more editor features rolled in:

1. Roslyn Format command.
   New POST /api/script-analysis/format runs Formatter.Format() from
   Microsoft.CodeAnalysis.CSharp.Workspaces on the parsed script
   tree. monaco-init.js registers a DocumentFormattingEditProvider
   so Ctrl/Cmd-Shift-F and the toolbar "Format" button both work.

2. Inlay hints with parameter names.
   New POST /api/script-analysis/inlay-hints walks CallShared /
   CallScript invocations and emits InlayHint records positioned at
   each argument with the matching parameter's name (e.g. "name:").
   Ghost text appears via Monaco's InlayHintsProvider.

3. SCADA005 argument-type diagnostic.
   Literal type vs. declared parameter type check on every
   CallShared/CallScript argument. Float accepts Integer literals;
   Object/List accept anything; null only matches reference-ish
   types. Legacy lowercase types ("string" etc) from the DB are
   normalized to the canonical set before comparison so existing
   data doesn't false-negative. Non-literal args (variables,
   expressions) are skipped — out of scope for a cheap pass.

4. Parameters["name"] hover.
   Hover endpoint now also resolves Parameters["X"] element-access
   keys against the form's DeclaredParameterShapes and returns
   "parameter `name: String`"-style markdown. MonacoEditor surfaces
   the new DeclaredParameterShapes parameter; ScriptParameterNames
   gets a ParseShapes companion.

5. Problems panel.
   Bootstrap card under the editor listing every marker with
   severity badge, line number, message, and SCADA / CS code. Click
   a row to scroll the editor to that line and focus. JS now
   invokes OnMarkersChanged on the .NET side whenever
   setModelMarkers fires, so the panel stays in sync with the
   editor.

6. Editor toolbar.
   Small top-right strip on each editor with Format / Wrap /
   Minimap / Theme toggles. New MonacoBlazor.format,
   setEditorOption, and revealLine JS APIs back the buttons and the
   problems-panel scroll-to-line.

Contracts:
  - FormatRequest / FormatResponse
  - InlayHintsRequest / InlayHintsResponse / InlayHint
  - HoverRequest.DeclaredParameters
  - MonacoEditor.DeclaredParameterShapes parameter
  - MonacoEditor.MarkersChanged callback
  - ScadaContext.DeclaredParameterShapes

10 new xUnit tests covering format, inlay hints, SCADA005 (string-
expects-integer, integer-expects-string, float-accepts-integer,
object-accepts-anything, non-literal-skipped), and Parameters key
hover. Total: 139 -> 149.

Microsoft.CodeAnalysis.CSharp.Workspaces 4.13.0 added to pull in
Formatter and AdhocWorkspace.

Browser-verified: typing `CallShared("Greet", 42)` now shows the
"name:" inlay hint and a SCADA005 squiggle on `42`; Parameters["typo"]
shows SCADA003 as before; the toolbar buttons all work.
2026-05-12 05:28:13 -04:00
Joseph Doherty 225817eac9 feat(ui/scripts): SCADA-specific Monaco extensions
Wave 3 of the Monaco/Roslyn integration. Adds the four extensions
agreed in the design Q&A:

  1. Parameters["..."] keys — when the cursor is inside a string
     literal that's the index of a Parameters[] element-access,
     completions return the parameter names declared in the form's
     ParameterListEditor.
  2. CallShared("...") names — when the cursor is inside a string
     literal argument to a CallShared(...) invocation, completions
     return the names of all shared scripts (resolved server-side
     via SharedScriptService).
  3. CallScript("...") names — same shape, but uses sibling-script
     names passed from the form (TemplateEdit's _scripts list).
  4. Forbidden-API diagnostic — squiggles uses of the documented
     script trust model bans: System.IO / Diagnostics / Reflection /
     Net / Threading.Thread namespaces, plus the named types File,
     Directory, Process, Thread, Socket, etc. New diagnostic codes
     SCADA001 (using directive) and SCADA002 (type identifier).

ScriptAnalysisService gains a SharedScriptService dependency
(scoped, hence the analyzer is now scoped too); CompletionsRequest
carries DeclaredParameters and SiblingScripts; Complete is now async.

MonacoEditor.razor exposes DeclaredParameters / SiblingScripts
parameters plus a [JSInvokable] GetContext() so the JS side asks
for the latest form state on every completion request. The
provider in monaco-init.js looks up the owning editor from the
internal editors map and forwards the context.

ScriptParameterNames helper parses the ParameterListEditor JSON
into a name list — used by SharedScriptForm, ApiMethodForm, and
TemplateEdit's Add-Script form to populate the Monaco context.

Smoke-verified via direct fetch + Monaco trigger:
  - var x = Parameters["  →  popup: "name" (declared parameter)
  - var y = CallShared("  →  popup: GetWeather, Greet
  - using System.IO;      →  SCADA001 squiggle
  - Process.Start(...)    →  SCADA002 squiggle
  - File.ReadAllText(...) →  SCADA002 squiggle

Also fixed: ScriptAnalysisService scoped (was singleton, broke DI
because SharedScriptService is scoped); JS normalizes Pascal-case
context keys from Blazor's record serialization to camel-case for
the request body.
2026-05-12 04:56:56 -04:00
Joseph Doherty 7f01c5547a feat(ui/design): Monaco editor for script code fields
Vendors Monaco 0.55.1 min/vs/ (~15 MB) at
wwwroot/lib/monaco/vs/. No CDN dependency; works on air-gapped
deployments. Loaded lazily on first script-edit via the AMD loader.

wwwroot/js/monaco-init.js exposes window.MonacoBlazor with
createEditor / setValue / getValue / setMarkers / dispose. Handles
loader bootstrap, DotNet round-trip on content change, and marker
sets for later diagnostic wiring.

Components/Shared/MonacoEditor.razor is a Blazor wrapper with
Value / ValueChanged / Language / Height / ReadOnly parameters and
IAsyncDisposable teardown. Bidirectional binding tracks
_lastSentValue to avoid push/pull loops.

Replaces the plain textareas in SharedScriptForm, TemplateEdit's
Add-Script form, and ApiMethodForm. Default height 320px ≈ the
previous rows=10. Build / tests / dialog flow unaffected.

Wave 1 of three. Roslyn-backed completions and SCADA-specific
extensions follow in subsequent commits.
2026-05-12 04:34:41 -04:00
Joseph Doherty 1b98d37919 refactor(ui/design): replace JSON inputs with structured editors
Two new shared components in Components/Shared:
  - ParameterListEditor: table of rows (name + type + item type + required + remove)
  - ReturnTypeEditor: single type (+ item type when List)

Both round-trip the same JSON shape already stored on the entity:
  parameters: [{"name":"x","type":"String","required":true},...]
  return:     {"type":"List","itemType":"Integer"} | null

Type set follows the Inbound API validator (Boolean, Integer, Float,
String, Object, List). Legacy values normalize on read — Int32 / int64
/ Double / Decimal / lowercase string / etc all coalesce to the new
set so existing rows render correctly. Re-saving persists the
normalized form.

Applied to:
  - SharedScriptForm
  - TemplateEdit Add Script form (also surfaces ParameterDefinitions
    + ReturnDefinition which the entity supported but the form was
    never wiring through)
  - ApiMethodForm

Graceful degradation: invalid JSON is shown with a "Start fresh"
escape hatch instead of crashing the form.
2026-05-12 04:22:58 -04:00
Joseph Doherty b6e2ec8a50 refactor(ui/design): card grid, SMTP split, TemplateEdit vertical-stack
Templates: <h4> in flex header, Expand/Collapse moved into a Bulk
actions dropdown, hover-visible kebab on tree nodes with aria-labels.
TreeView CSS gets a .tv-kebab opacity-on-hover utility.

TemplateCreate: form-control (not -sm) for primary inputs; accessible
Back button.

TemplateEdit: Properties card vertical-stacked with Save at the
bottom-right and Parent rendered as readonly plaintext. Add-member
forms (Attributes, Alarms, Scripts, Compositions) reflowed from
horizontal row g-2 align-items-end into cards with stacked col-12
inputs (Scripts gets rows=10). Lock/Unlock badges show full words.
Per-row Delete moved into a kebab dropdown. Tab nav gains
role="tablist" / role="tab" / aria-selected / aria-controls and panels
get role="tabpanel". Validation entries get consistent strong-and-
muted styling.

SharedScripts: migrated from table to card grid (col-lg-6) matching
Sites; cards show code preview + param/return badges + Edit + kebab.
Search filter, empty state CTA, @key.

SharedScriptForm: small ?-icon tooltips next to Parameters and Return
Definition labels.

ExternalSystems: SMTP split out to its own page; remaining tabs (
External Systems, DB Connections, Notification Lists, API Methods,
API Keys) unified as card grids with per-tab search + empty-state CTA.
Tab nav gets full ARIA instrumentation. Header gains a link to the
new SMTP page.

New page SmtpConfiguration.razor at /design/smtp: vertical-stacked
form using the existing Credentials field on the entity.

ExternalSystemForm: AuthConfig placeholder updates based on the
selected AuthType (None / ApiKey / BasicAuth).

DbConnectionForm: form-text below Connection String noting that the
value is stored in plain text and is admin-only.

ApiMethodForm: Script textarea rows=10; JSON example placeholders
for Params and Returns.

NotificationListForm: form-control sizing on Name/email inputs;
thead.table-dark -> table-light on the recipients table.
2026-05-12 03:32:39 -04:00
Joseph Doherty d3194e3634 feat: separate create/edit form pages, Playwright test infrastructure, /auth/token endpoint
Move all CRUD create/edit forms from inline on list pages to dedicated form pages
with back-button navigation and post-save redirect. Add Playwright Docker container
(browser server on port 3000) with 25 passing E2E tests covering login, navigation,
and site CRUD workflows. Add POST /auth/token endpoint for clean JWT retrieval.
2026-03-21 15:17:24 -04:00