systemlink-cli 1.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slcli/__init__.py +1 -0
- slcli/__main__.py +23 -0
- slcli/_version.py +4 -0
- slcli/asset_click.py +1289 -0
- slcli/cli_formatters.py +218 -0
- slcli/cli_utils.py +504 -0
- slcli/comment_click.py +602 -0
- slcli/completion_click.py +418 -0
- slcli/config.py +81 -0
- slcli/config_click.py +498 -0
- slcli/dff_click.py +979 -0
- slcli/dff_decorators.py +24 -0
- slcli/example_click.py +404 -0
- slcli/example_loader.py +274 -0
- slcli/example_provisioner.py +2777 -0
- slcli/examples/README.md +134 -0
- slcli/examples/_schema/schema-v1.0.json +169 -0
- slcli/examples/demo-complete-workflow/README.md +323 -0
- slcli/examples/demo-complete-workflow/config.yaml +638 -0
- slcli/examples/demo-test-plans/README.md +132 -0
- slcli/examples/demo-test-plans/config.yaml +154 -0
- slcli/examples/exercise-5-1-parametric-insights/README.md +101 -0
- slcli/examples/exercise-5-1-parametric-insights/config.yaml +1589 -0
- slcli/examples/exercise-7-1-test-plans/README.md +93 -0
- slcli/examples/exercise-7-1-test-plans/config.yaml +323 -0
- slcli/examples/spec-compliance-notebooks/README.md +140 -0
- slcli/examples/spec-compliance-notebooks/config.yaml +112 -0
- slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +1553 -0
- slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +1577 -0
- slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +912 -0
- slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- slcli/feed_click.py +892 -0
- slcli/file_click.py +932 -0
- slcli/function_click.py +1400 -0
- slcli/function_templates.py +85 -0
- slcli/main.py +406 -0
- slcli/mcp_click.py +269 -0
- slcli/mcp_server.py +748 -0
- slcli/notebook_click.py +1770 -0
- slcli/platform.py +345 -0
- slcli/policy_click.py +679 -0
- slcli/policy_utils.py +411 -0
- slcli/profiles.py +411 -0
- slcli/response_handlers.py +359 -0
- slcli/routine_click.py +763 -0
- slcli/skill_click.py +253 -0
- slcli/skills/slcli/SKILL.md +713 -0
- slcli/skills/slcli/references/analysis-recipes.md +474 -0
- slcli/skills/slcli/references/filtering.md +236 -0
- slcli/skills/systemlink-webapp/SKILL.md +744 -0
- slcli/skills/systemlink-webapp/references/deployment.md +123 -0
- slcli/skills/systemlink-webapp/references/nimble-angular.md +380 -0
- slcli/skills/systemlink-webapp/references/systemlink-services.md +192 -0
- slcli/ssl_trust.py +93 -0
- slcli/system_click.py +2216 -0
- slcli/table_utils.py +124 -0
- slcli/tag_click.py +794 -0
- slcli/templates_click.py +599 -0
- slcli/testmonitor_click.py +1667 -0
- slcli/universal_handlers.py +305 -0
- slcli/user_click.py +1218 -0
- slcli/utils.py +832 -0
- slcli/web_editor.py +295 -0
- slcli/webapp_click.py +981 -0
- slcli/workflow_preview.py +287 -0
- slcli/workflows_click.py +988 -0
- slcli/workitem_click.py +2258 -0
- slcli/workspace_click.py +576 -0
- slcli/workspace_utils.py +206 -0
- systemlink_cli-1.3.1.dist-info/METADATA +20 -0
- systemlink_cli-1.3.1.dist-info/RECORD +74 -0
- systemlink_cli-1.3.1.dist-info/WHEEL +4 -0
- systemlink_cli-1.3.1.dist-info/entry_points.txt +7 -0
- systemlink_cli-1.3.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Deployment Reference — slcli webapp commands
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- `slcli` installed and authenticated (`slcli login` or config file present)
|
|
6
|
+
- Angular app built to `dist/<app-name>/browser/`
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Build
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Run from project root
|
|
14
|
+
node_modules/.bin/ng build --configuration production --output-path dist/<app-name>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**Do NOT pass `--base-href`.** This would reintroduce a `<base>` element that violates SystemLink's CSP.
|
|
18
|
+
|
|
19
|
+
Angular 19 places the browser output at `dist/<app-name>/browser/` — publish that subdirectory, not the parent.
|
|
20
|
+
|
|
21
|
+
### Background build (if terminal has heredoc/interrupt issues)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
nohup node_modules/.bin/ng build --configuration production --output-path dist/<app-name> \
|
|
25
|
+
> /tmp/ng-build.log 2>&1 &
|
|
26
|
+
echo "Build PID: $!"
|
|
27
|
+
# Check progress:
|
|
28
|
+
tail -f /tmp/ng-build.log
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## First deploy (no existing webapp)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
slcli webapp publish dist/<app-name>/browser/ -w <workspace-name>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The command prints the new webapp ID. **Save it** — you need it for every future redeploy and for `slcli webapp open`.
|
|
40
|
+
|
|
41
|
+
Example output:
|
|
42
|
+
```
|
|
43
|
+
Created webapp: 3727d9ac-86e1-4d6e-820e-d2630c1b28e9
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Redeploy (update existing webapp)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
slcli webapp publish dist/<app-name>/browser/ -w <workspace-name> -i <webapp-id>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Open in browser
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
slcli webapp open -i <webapp-id>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## List webapps
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
slcli webapp list -w <workspace-name>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Delete a webapp
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
slcli webapp delete -i <webapp-id>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Deployment checklist
|
|
81
|
+
|
|
82
|
+
Before publishing, verify:
|
|
83
|
+
|
|
84
|
+
- [ ] `index.html` has **no** `<base href>` tag
|
|
85
|
+
- [ ] `app.module.ts` provides `{ provide: APP_BASE_HREF, useValue: '/' }`
|
|
86
|
+
- [ ] `app-routing.module.ts` uses `useHash: true`
|
|
87
|
+
- [ ] `angular.json` has `inlineCritical: false` in production optimization
|
|
88
|
+
- [ ] `basePath` is `window.location.origin + '/<service-prefix>'` (not just origin)
|
|
89
|
+
- [ ] `credentials: 'include'` (or equivalent) set on API client
|
|
90
|
+
- [ ] Build succeeded with no errors (warnings about bundle size are OK if within 2MB error limit)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Common deployment errors
|
|
95
|
+
|
|
96
|
+
### App shows blank/white screen
|
|
97
|
+
|
|
98
|
+
- Check browser console for NG04002 → `useHash: true` missing
|
|
99
|
+
- Check for CSP `base-uri` error → remove `<base>` tag
|
|
100
|
+
|
|
101
|
+
### API calls fail with status 0 (CORS)
|
|
102
|
+
|
|
103
|
+
- `basePath` is pointing to a different origin than where the app is served
|
|
104
|
+
- Fix: use `window.location.origin + '/service-prefix'`
|
|
105
|
+
|
|
106
|
+
### API calls return 404 on correct paths
|
|
107
|
+
|
|
108
|
+
- `basePath` is missing the service prefix (e.g., `/nitag`)
|
|
109
|
+
- The generated client's `defaultBasePath` is being overridden by your `Configuration` — make sure your `basePath` value includes the full prefix
|
|
110
|
+
|
|
111
|
+
### Styles look broken or CSP reports `unsafe-inline`
|
|
112
|
+
|
|
113
|
+
- Beasties CSS inliner is injecting `onload` handlers
|
|
114
|
+
- Fix: set `inlineCritical: false` in angular.json
|
|
115
|
+
|
|
116
|
+
### "Budget exceeded" build error
|
|
117
|
+
|
|
118
|
+
Increase error limits in `angular.json`:
|
|
119
|
+
```json
|
|
120
|
+
"budgets": [
|
|
121
|
+
{ "type": "initial", "maximumWarning": "1MB", "maximumError": "2MB" }
|
|
122
|
+
]
|
|
123
|
+
```
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# Nimble Angular — Template & Usage Reference
|
|
2
|
+
|
|
3
|
+
## nimble-theme-provider
|
|
4
|
+
|
|
5
|
+
Wrap your entire app. Always place at the root component level.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<nimble-theme-provider [theme]="currentTheme">
|
|
9
|
+
<router-outlet></router-outlet>
|
|
10
|
+
</nimble-theme-provider>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Themes: `light`, `dark`, `color` (high contrast).
|
|
14
|
+
|
|
15
|
+
For SystemLink-hosted apps, do not hard-code `theme="light"` unless the user explicitly wants a fixed theme. The common pattern is:
|
|
16
|
+
|
|
17
|
+
1. Detect initial theme from `?theme=...`, then the parent frame's `nimble-theme-provider`, then local storage, then system preference
|
|
18
|
+
2. If the app is hosted in a same-origin iframe, watch the parent provider's `theme` attribute with `MutationObserver` and update `currentTheme`
|
|
19
|
+
3. Define theme-aware CSS aliases such as colors and shadows on `nimble-theme-provider`, not on `:root`, so token resolution follows the active Nimble theme
|
|
20
|
+
|
|
21
|
+
When debugging theme mismatches, inspect resolved token values on the provider with `getComputedStyle(provider).getPropertyValue('--ni-nimble-application-background-color')` rather than only checking the `theme` attribute.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## nimble-table
|
|
26
|
+
|
|
27
|
+
Displays tabular data. Data must be an `Observable<TableRecord[]>` bound with `[data$]`.
|
|
28
|
+
|
|
29
|
+
### Module
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { NimbleTableModule } from "@ni/nimble-angular/table";
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Row type requirement
|
|
36
|
+
|
|
37
|
+
Your row type must satisfy `TableRecord`. Add an index signature:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
interface MyRow {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
value: string | undefined;
|
|
44
|
+
[key: string]: FieldValue | undefined; // required for TableRecord compatibility
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Template
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<nimble-table
|
|
52
|
+
[data$]="rows$"
|
|
53
|
+
id-field-name="id"
|
|
54
|
+
selection-mode="single"
|
|
55
|
+
(selection-change)="onSelectionChange($event)"
|
|
56
|
+
>
|
|
57
|
+
<nimble-table-column-text field-name="name" column-id="col-name"
|
|
58
|
+
>Name</nimble-table-column-text
|
|
59
|
+
>
|
|
60
|
+
|
|
61
|
+
<nimble-table-column-text field-name="value" column-id="col-value"
|
|
62
|
+
>Value</nimble-table-column-text
|
|
63
|
+
>
|
|
64
|
+
</nimble-table>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Component wiring
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { TableRecord, TableRowSelectionEventDetail } from '@ni/nimble-angular/table';
|
|
71
|
+
import { BehaviorSubject } from 'rxjs';
|
|
72
|
+
|
|
73
|
+
rows$ = new BehaviorSubject<MyRow[]>([]);
|
|
74
|
+
|
|
75
|
+
onSelectionChange(event: CustomEvent<TableRowSelectionEventDetail<MyRow>>): void {
|
|
76
|
+
const selected = event.detail.selectedRecords[0];
|
|
77
|
+
// ...
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## nimble-table-column-text
|
|
84
|
+
|
|
85
|
+
Simple string column. Import: `NimbleTableColumnTextModule` from `@ni/nimble-angular/table-column/text`.
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<nimble-table-column-text field-name="myField" column-id="col-1">
|
|
89
|
+
Column Header
|
|
90
|
+
</nimble-table-column-text>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## nimble-button
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { NimbleButtonModule } from "@ni/nimble-angular";
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<!-- Default -->
|
|
103
|
+
<nimble-button (click)="doSomething()">Click Me</nimble-button>
|
|
104
|
+
|
|
105
|
+
<!-- Accent/primary style -->
|
|
106
|
+
<nimble-button
|
|
107
|
+
appearance="block"
|
|
108
|
+
appearance-variant="accent"
|
|
109
|
+
(click)="doSomething()"
|
|
110
|
+
>
|
|
111
|
+
Primary Action
|
|
112
|
+
</nimble-button>
|
|
113
|
+
|
|
114
|
+
<!-- Ghost / low-emphasis -->
|
|
115
|
+
<nimble-button appearance="ghost" (click)="cancel()">Cancel</nimble-button>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> **Note:** `appearance="accent"` is NOT valid. Use `appearance="block" appearance-variant="accent"`.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## nimble-anchor-tabs + nimble-anchor-tab
|
|
123
|
+
|
|
124
|
+
Use for top-level page navigation. Bind `[activeid]` from component state; update it by tracking `NavigationEnd` router events.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import {
|
|
128
|
+
NimbleAnchorTabsModule,
|
|
129
|
+
NimbleAnchorTabModule,
|
|
130
|
+
} from "@ni/nimble-angular";
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<nimble-anchor-tabs [activeid]="activeTabId">
|
|
135
|
+
<nimble-anchor-tab id="catalog" nimbleRouterLink="/catalog"
|
|
136
|
+
>Catalog</nimble-anchor-tab
|
|
137
|
+
>
|
|
138
|
+
<nimble-anchor-tab id="installed" nimbleRouterLink="/installed"
|
|
139
|
+
>Installed</nimble-anchor-tab
|
|
140
|
+
>
|
|
141
|
+
<nimble-anchor-tab id="settings" nimbleRouterLink="/settings"
|
|
142
|
+
>Settings</nimble-anchor-tab
|
|
143
|
+
>
|
|
144
|
+
</nimble-anchor-tabs>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Track active tab in the component (see `SKILL.md → Step 9` for full `NavigationEnd` subscription pattern).
|
|
148
|
+
|
|
149
|
+
> Do not use `<nimble-tabs>` + `<nimble-tab-panel>` for navigation — that pattern is for tabbed content within a single page, not routing.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## nimble-text-field
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { NimbleTextFieldModule } from "@ni/nimble-angular";
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```html
|
|
160
|
+
<!-- Basic -->
|
|
161
|
+
<nimble-text-field
|
|
162
|
+
[(ngModel)]="filterValue"
|
|
163
|
+
placeholder="Enter filter..."
|
|
164
|
+
(ngModelChange)="onFilterChange()"
|
|
165
|
+
>
|
|
166
|
+
Filter
|
|
167
|
+
</nimble-text-field>
|
|
168
|
+
|
|
169
|
+
<!-- With icon prefix (slot="start") -->
|
|
170
|
+
<nimble-text-field
|
|
171
|
+
placeholder="Search…"
|
|
172
|
+
[(ngModel)]="searchTerm"
|
|
173
|
+
(ngModelChange)="applyFilters()"
|
|
174
|
+
>
|
|
175
|
+
<nimble-icon-magnifying-glass slot="start"></nimble-icon-magnifying-glass>
|
|
176
|
+
</nimble-text-field>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Import icon modules from the **main `@ni/nimble-angular` barrel** — icon-specific sub-paths do not exist:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
import { NimbleIconMagnifyingGlassModule } from "@ni/nimble-angular";
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
> Use `(ngModelChange)` rather than `(change)` for reactive value handling. `(change)` fires on blur only; `(ngModelChange)` fires on every keystroke.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## nimble-select + nimble-list-option
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { NimbleSelectModule, NimbleListOptionModule } from "@ni/nimble-angular";
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<!-- Basic -->
|
|
197
|
+
<nimble-select [(ngModel)]="selectedType" (ngModelChange)="onTypeChange()">
|
|
198
|
+
<nimble-list-option value="">All types</nimble-list-option>
|
|
199
|
+
<nimble-list-option value="DOUBLE">Double</nimble-list-option>
|
|
200
|
+
<nimble-list-option value="STRING">String</nimble-list-option>
|
|
201
|
+
<nimble-list-option value="BOOLEAN">Boolean</nimble-list-option>
|
|
202
|
+
</nimble-select>
|
|
203
|
+
|
|
204
|
+
<!-- With built-in filter (useful for long lists) -->
|
|
205
|
+
<nimble-select filter-mode="standard" [(ngModel)]="selectedWorkspace">
|
|
206
|
+
<nimble-list-option *ngFor="let ws of workspaces" [value]="ws.id"
|
|
207
|
+
>{{ ws.name }}</nimble-list-option
|
|
208
|
+
>
|
|
209
|
+
</nimble-select>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
> Use `filter-mode="standard"` to add a built-in text filter to the dropdown — no custom search logic needed for long option lists.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## nimble-drawer
|
|
217
|
+
|
|
218
|
+
Side panel for details or config. Control with `#drawerRef` template variable.
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import { NimbleDrawerModule } from "@ni/nimble-angular";
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```html
|
|
225
|
+
<nimble-drawer #detailDrawer location="right">
|
|
226
|
+
<h3 slot="header">Detail</h3>
|
|
227
|
+
<div>{{ selectedItem?.name }}</div>
|
|
228
|
+
<nimble-button slot="footer" (click)="detailDrawer.hide()"
|
|
229
|
+
>Close</nimble-button
|
|
230
|
+
>
|
|
231
|
+
</nimble-drawer>
|
|
232
|
+
|
|
233
|
+
<nimble-button (click)="detailDrawer.show()">Open Detail</nimble-button>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## nimble-spinner
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { NimbleSpinnerModule } from "@ni/nimble-angular";
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
```html
|
|
245
|
+
<nimble-spinner *ngIf="loading"></nimble-spinner>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## nimble-banner
|
|
251
|
+
|
|
252
|
+
For in-page error/warning/info messages.
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { NimbleBannerModule } from "@ni/nimble-angular";
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
```html
|
|
259
|
+
<nimble-banner *ngIf="error" severity="error" [open]="!!error">
|
|
260
|
+
{{ error }}
|
|
261
|
+
</nimble-banner>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## nimble-dialog
|
|
267
|
+
|
|
268
|
+
**Critical:** never put `*ngIf` on a `nimble-dialog`. The `*ngIf="false"` removes the element from the DOM, making `@ViewChild` return `undefined` and `.show()` impossible to invoke.
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
import { NimbleDialogModule } from "@ni/nimble-angular";
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<!-- Keep the dialog always in the DOM -->
|
|
276
|
+
<nimble-dialog #confirmDialog>
|
|
277
|
+
<span slot="title">Confirm Action</span>
|
|
278
|
+
<span slot="subtitle">This cannot be undone.</span>
|
|
279
|
+
|
|
280
|
+
<p>Are you sure you want to proceed?</p>
|
|
281
|
+
|
|
282
|
+
<!-- Multiple slot="footer" elements are displayed side by side -->
|
|
283
|
+
<nimble-button slot="footer" (click)="closeDialog()">Cancel</nimble-button>
|
|
284
|
+
<nimble-button
|
|
285
|
+
slot="footer"
|
|
286
|
+
appearance="block"
|
|
287
|
+
appearance-variant="accent"
|
|
288
|
+
(click)="confirm()"
|
|
289
|
+
>Confirm</nimble-button
|
|
290
|
+
>
|
|
291
|
+
</nimble-dialog>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
```typescript
|
|
295
|
+
import { ElementRef, ViewChild } from '@angular/core';
|
|
296
|
+
|
|
297
|
+
@ViewChild('confirmDialog') private dialogEl?: ElementRef;
|
|
298
|
+
|
|
299
|
+
openDialog(): void { this.dialogEl?.nativeElement.show(); }
|
|
300
|
+
closeDialog(): void { this.dialogEl?.nativeElement.close(); }
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Available slots:** `title` (required text), `subtitle` (optional descriptive text), `footer` (action buttons — multiple allowed).
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Layout patterns
|
|
308
|
+
|
|
309
|
+
Nimble doesn't ship a grid/layout component. Use flexbox in SCSS:
|
|
310
|
+
|
|
311
|
+
```scss
|
|
312
|
+
// component.scss
|
|
313
|
+
:host {
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
height: 100%;
|
|
317
|
+
padding: 16px;
|
|
318
|
+
box-sizing: border-box;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.toolbar {
|
|
322
|
+
display: flex;
|
|
323
|
+
gap: 8px;
|
|
324
|
+
align-items: flex-end;
|
|
325
|
+
margin-bottom: 12px;
|
|
326
|
+
flex-wrap: wrap;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.table-container {
|
|
330
|
+
flex: 1;
|
|
331
|
+
min-height: 0; // important — lets flex child shrink below its content height
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
nimble-table {
|
|
335
|
+
height: 100%;
|
|
336
|
+
}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Clickable card / list-row pattern
|
|
340
|
+
|
|
341
|
+
For any clickable tile or row, use Nimble tokens directly (not the `--sl-app-color-border` alias which is for dividers):
|
|
342
|
+
|
|
343
|
+
```scss
|
|
344
|
+
.card {
|
|
345
|
+
border: 1px solid var(--ni-nimble-card-border-color);
|
|
346
|
+
background: var(--ni-nimble-section-background-color);
|
|
347
|
+
border-radius: var(--ni-nimble-small-padding, 4px);
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
// Transition both shadow and border for a polished hover
|
|
350
|
+
transition:
|
|
351
|
+
box-shadow var(--ni-nimble-medium-delay, 0.15s) ease,
|
|
352
|
+
border-color var(--ni-nimble-medium-delay, 0.15s) ease;
|
|
353
|
+
|
|
354
|
+
&:hover {
|
|
355
|
+
box-shadow: var(
|
|
356
|
+
--ni-nimble-elevation-2-box-shadow,
|
|
357
|
+
0 2px 8px rgba(0, 0, 0, 0.12)
|
|
358
|
+
);
|
|
359
|
+
border-color: var(--ni-nimble-border-hover-color);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// For equal-height cards in a CSS grid, the host element must fill the cell:
|
|
364
|
+
:host {
|
|
365
|
+
display: flex;
|
|
366
|
+
height: 100%;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.card {
|
|
370
|
+
flex: 1; // fills the :host flex container
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.card-body {
|
|
374
|
+
flex: 1; // pushes footer to the bottom
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.card-description {
|
|
378
|
+
flex: 1; // equalises description height across cards
|
|
379
|
+
}
|
|
380
|
+
```
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# SystemLink Services — API Reference
|
|
2
|
+
|
|
3
|
+
## How to find a service's OpenAPI spec
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
https://<server>/<service-prefix>/swagger/v2/<service-name>.yaml
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Examples:
|
|
10
|
+
- `https://myserver.com/nitag/swagger/v2/nitag.yaml`
|
|
11
|
+
- `https://myserver.com/nitest/swagger/v2/nitest.yaml`
|
|
12
|
+
|
|
13
|
+
Use the spec URL as the `input` in your `openapi-ts.config.ts`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Tag Historian (`/nitag`)
|
|
18
|
+
|
|
19
|
+
**Base URL:** `window.location.origin + '/nitag'`
|
|
20
|
+
|
|
21
|
+
### Key endpoints
|
|
22
|
+
|
|
23
|
+
| Operation | Method | Path |
|
|
24
|
+
|-----------|--------|------|
|
|
25
|
+
| Query tags with current values | POST | `/nitag/v2/query-tags-with-values` |
|
|
26
|
+
| Get single tag | GET | `/nitag/v2/tags/{path}` |
|
|
27
|
+
| Write tag value | PUT | `/nitag/v2/tags/{path}/values/current` |
|
|
28
|
+
| Query tag history | POST | `/nitag/v2/history-data/query-decimated-data` |
|
|
29
|
+
|
|
30
|
+
### Query body (`POST /query-tags-with-values`)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
{
|
|
34
|
+
filter: 'path = "system.*" and type = "DOUBLE"', // LINQ filter string
|
|
35
|
+
take: 1000, // max records to return
|
|
36
|
+
orderBy: 'TIMESTAMP', // PATH | VALUE | TIMESTAMP | WORKSPACE
|
|
37
|
+
descending: true,
|
|
38
|
+
// projection: [...] // AVOID — flattens the response and breaks tag.path / tag.type access
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Response shape (`TagWithValue`)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
{
|
|
46
|
+
tag: {
|
|
47
|
+
path: string,
|
|
48
|
+
type: 'DOUBLE' | 'INT' | 'STRING' | 'BOOLEAN' | 'U_INT64' | 'DATE_TIME',
|
|
49
|
+
keywords: string[],
|
|
50
|
+
properties: Record<string, string>,
|
|
51
|
+
workspace: string,
|
|
52
|
+
lastUpdated: string,
|
|
53
|
+
},
|
|
54
|
+
current: {
|
|
55
|
+
value: { value: string | number | boolean },
|
|
56
|
+
timestamp: string,
|
|
57
|
+
},
|
|
58
|
+
aggregates: { ... }
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
> **Gotcha:** If you include `projection` in the query, the response is flattened — `tag` nesting is removed and `path`, `type` etc. lift to the top level. This breaks `twv.tag?.path`. Omit `projection` unless you update all mapping code to match the flat shape.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Test Monitor (`/nitest`)
|
|
67
|
+
|
|
68
|
+
**Base URL:** `window.location.origin + '/nitest'`
|
|
69
|
+
|
|
70
|
+
### Key endpoints
|
|
71
|
+
|
|
72
|
+
| Operation | Method | Path |
|
|
73
|
+
|-----------|--------|------|
|
|
74
|
+
| Query results | POST | `/nitest/v2/query-results` |
|
|
75
|
+
| Get result by ID | GET | `/nitest/v2/results/{id}` |
|
|
76
|
+
| Query steps | POST | `/nitest/v2/query-steps` |
|
|
77
|
+
| Get products | GET | `/nitest/v2/products` |
|
|
78
|
+
|
|
79
|
+
### Query body
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
{
|
|
83
|
+
filter: 'startedWithin = "7d"',
|
|
84
|
+
orderBy: 'startedAt',
|
|
85
|
+
descending: true,
|
|
86
|
+
take: 200,
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Asset Management (`/niapm`)
|
|
93
|
+
|
|
94
|
+
**Base URL:** `window.location.origin + '/niapm'`
|
|
95
|
+
|
|
96
|
+
### Key endpoints
|
|
97
|
+
|
|
98
|
+
| Operation | Method | Path |
|
|
99
|
+
|-----------|--------|------|
|
|
100
|
+
| Query assets | POST | `/niapm/v1/query-assets` |
|
|
101
|
+
| Get asset | GET | `/niapm/v1/assets/{id}` |
|
|
102
|
+
| Get calibration forecast | GET | `/niapm/v1/assets/{id}/policies` |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Systems Management (`/nisysmgmt`)
|
|
107
|
+
|
|
108
|
+
**Base URL:** `window.location.origin + '/nisysmgmt'`
|
|
109
|
+
|
|
110
|
+
### Key endpoints
|
|
111
|
+
|
|
112
|
+
| Operation | Method | Path |
|
|
113
|
+
|-----------|--------|------|
|
|
114
|
+
| Get systems | GET | `/nisysmgmt/v1/systems` |
|
|
115
|
+
| Query systems | POST | `/nisysmgmt/v1/query-systems` |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Work Orders (`/niworkorder`)
|
|
120
|
+
|
|
121
|
+
**Base URL:** `window.location.origin + '/niworkorder'`
|
|
122
|
+
|
|
123
|
+
### Key endpoints
|
|
124
|
+
|
|
125
|
+
| Operation | Method | Path |
|
|
126
|
+
|-----------|--------|------|
|
|
127
|
+
| Query work orders | POST | `/niworkorder/v1/query` |
|
|
128
|
+
| Update work order | PATCH | `/niworkorder/v1/work-orders/{id}` |
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Authentication patterns
|
|
133
|
+
|
|
134
|
+
### Same-origin (recommended for deployed webapps)
|
|
135
|
+
|
|
136
|
+
The app is served by SystemLink and calls SystemLink. Browser session cookies are sent automatically if `credentials: 'include'` is set on fetch requests.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// hey-api client setup
|
|
140
|
+
import { createClient } from '@hey-api/client-fetch';
|
|
141
|
+
|
|
142
|
+
export const apiClient = createClient({
|
|
143
|
+
baseUrl: `${window.location.origin}/nitag`,
|
|
144
|
+
credentials: 'include',
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
No API key needed. The user must be logged in to SystemLink in that browser tab.
|
|
149
|
+
|
|
150
|
+
### API key (for dev/remote scenarios)
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
export const apiClient = createClient({
|
|
154
|
+
baseUrl: `${window.location.origin}/nitag`,
|
|
155
|
+
headers: {
|
|
156
|
+
'x-ni-api-key': localStorage.getItem('sl_api_key') ?? '',
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Let the user enter their API key in a config UI (a Nimble drawer works well). Store in `localStorage`. Never hardcode keys.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## LINQ filter syntax (used by tags, test, and other services)
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
path = "system.cpu.usage" exact match
|
|
169
|
+
path.StartsWith("system.") prefix match
|
|
170
|
+
type = "DOUBLE" enum match
|
|
171
|
+
lastUpdated > "2024-01-01" date comparison
|
|
172
|
+
path = "cpu" and type = "DOUBLE" AND
|
|
173
|
+
path = "cpu" or path = "mem" OR
|
|
174
|
+
keywords.Contains("production") array contains
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
String values must be double-quoted in the filter string. Build filters by joining parts with ` and `.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Error handling
|
|
182
|
+
|
|
183
|
+
SystemLink APIs return standard HTTP status codes. Common ones:
|
|
184
|
+
|
|
185
|
+
| Code | Meaning |
|
|
186
|
+
|------|---------|
|
|
187
|
+
| 401 | Not authenticated — user needs to log in, or API key is invalid |
|
|
188
|
+
| 403 | Authenticated but not authorized for this workspace/resource |
|
|
189
|
+
| 404 | Resource not found |
|
|
190
|
+
| 422 | Invalid request body (e.g., bad LINQ filter) |
|
|
191
|
+
|
|
192
|
+
Show errors in a `<nimble-banner severity="error">` with the status code and message from the response body.
|