systemlink-cli 1.4.1__tar.gz → 1.4.2__tar.gz
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.
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/PKG-INFO +1 -1
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/pyproject.toml +1 -1
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/_version.py +1 -1
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/config_click.py +39 -10
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/main.py +29 -21
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/platform.py +151 -62
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/slcli/SKILL.md +1 -1
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/system_click.py +11 -8
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/LICENSE +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/dff-editor/index.html +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/__init__.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/__main__.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/config.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/example_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/demo-complete-workflow/README.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/demo-test-plans/README.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/demo-test-plans/config.yaml +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/file_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/function_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/mcp_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/mcp_server.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/profiles.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/systemlink-webapp/SKILL.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/user_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/utils.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/webapp_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/workitem_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/workspace_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "systemlink-cli"
|
|
3
|
-
version = "1.4.
|
|
3
|
+
version = "1.4.2"
|
|
4
4
|
description = "SystemLink Integrator CLI - cross-platform CLI for SystemLink workflows and templates."
|
|
5
5
|
authors = ["Fred Visser <fred.visser@emerson.com>"]
|
|
6
6
|
packages = [{ include = "slcli" }]
|
|
@@ -8,7 +8,11 @@ from typing import Any, Optional
|
|
|
8
8
|
import click
|
|
9
9
|
import questionary
|
|
10
10
|
|
|
11
|
-
from .platform import
|
|
11
|
+
from .platform import (
|
|
12
|
+
PLATFORM_SLE,
|
|
13
|
+
PLATFORM_SLS,
|
|
14
|
+
check_service_status,
|
|
15
|
+
)
|
|
12
16
|
from .profiles import ProfileConfig, Profile, check_config_file_permissions
|
|
13
17
|
from .table_utils import output_formatted_list
|
|
14
18
|
from .utils import ExitCodes
|
|
@@ -83,16 +87,41 @@ def _add_profile_impl(
|
|
|
83
87
|
click.echo("⚠️ Warning: Adding HTTPS protocol to web URL.")
|
|
84
88
|
web_url = f"https://{web_url}"
|
|
85
89
|
|
|
86
|
-
# Detect platform type
|
|
87
|
-
click.echo("
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
click.echo("
|
|
90
|
+
# Detect platform type and check service status
|
|
91
|
+
click.echo("Checking server connectivity and services...")
|
|
92
|
+
status = check_service_status(url, api_key.strip())
|
|
93
|
+
platform = status["platform"]
|
|
94
|
+
|
|
95
|
+
if not status["server_reachable"]:
|
|
96
|
+
click.echo(" ⚠️ Could not connect to server", err=True)
|
|
97
|
+
click.echo(" Verify the URL is correct and the server is reachable.", err=True)
|
|
98
|
+
click.echo(
|
|
99
|
+
" Profile will be saved — run login again when the server is available.",
|
|
100
|
+
err=True,
|
|
101
|
+
)
|
|
94
102
|
else:
|
|
95
|
-
|
|
103
|
+
if platform == PLATFORM_SLE:
|
|
104
|
+
click.echo(" Platform: SystemLink Enterprise (Cloud)")
|
|
105
|
+
elif platform == PLATFORM_SLS:
|
|
106
|
+
click.echo(" Platform: SystemLink Server (On-Premises)")
|
|
107
|
+
else:
|
|
108
|
+
click.echo(" Platform: Unknown (will attempt all features)")
|
|
109
|
+
|
|
110
|
+
# Report authorization status
|
|
111
|
+
if status["auth_valid"] is False:
|
|
112
|
+
click.echo(" ⚠️ API key: Unauthorized — check that the key is valid", err=True)
|
|
113
|
+
elif status["auth_valid"] is True:
|
|
114
|
+
click.echo(" API key: ✓ Authorized")
|
|
115
|
+
|
|
116
|
+
# Report individual service status
|
|
117
|
+
services = status.get("services", {})
|
|
118
|
+
problem_services = [
|
|
119
|
+
name for name, svc_status in services.items() if svc_status == "unauthorized"
|
|
120
|
+
]
|
|
121
|
+
if problem_services and status["auth_valid"] is not False:
|
|
122
|
+
# Only show per-service issues if overall auth isn't completely invalid
|
|
123
|
+
for svc_name in problem_services:
|
|
124
|
+
click.echo(f" ⚠️ {svc_name}: unauthorized", err=True)
|
|
96
125
|
|
|
97
126
|
# Get default workspace (optional)
|
|
98
127
|
if workspace is None:
|
|
@@ -21,7 +21,6 @@ from .function_click import register_function_commands
|
|
|
21
21
|
from .mcp_click import register_mcp_commands
|
|
22
22
|
from .notebook_click import register_notebook_commands
|
|
23
23
|
from .platform import (
|
|
24
|
-
PLATFORM_UNKNOWN,
|
|
25
24
|
get_platform_info,
|
|
26
25
|
)
|
|
27
26
|
from .policy_click import register_policy_commands
|
|
@@ -295,11 +294,12 @@ def logout(profile: Optional[str], remove_all: bool, force: bool) -> None:
|
|
|
295
294
|
|
|
296
295
|
@cli.command()
|
|
297
296
|
@click.option("--format", "-f", type=click.Choice(["table", "json"]), default="table")
|
|
298
|
-
|
|
297
|
+
@click.option("--skip-health", is_flag=True, default=False, help="Skip live service health checks.")
|
|
298
|
+
def info(format: str, skip_health: bool) -> None:
|
|
299
299
|
"""Show current configuration and detected platform."""
|
|
300
300
|
from .profiles import ProfileConfig, get_active_profile
|
|
301
301
|
|
|
302
|
-
platform_info = get_platform_info()
|
|
302
|
+
platform_info = get_platform_info(skip_health=skip_health)
|
|
303
303
|
|
|
304
304
|
# Add profile information
|
|
305
305
|
cfg = ProfileConfig.load()
|
|
@@ -333,7 +333,14 @@ def info(format: str) -> None:
|
|
|
333
333
|
click.echo("├" + "─" * content_width + "┤")
|
|
334
334
|
|
|
335
335
|
# Connection status
|
|
336
|
-
|
|
336
|
+
if not platform_info["logged_in"]:
|
|
337
|
+
status = "✗ Not logged in"
|
|
338
|
+
elif platform_info.get("server_reachable") is False:
|
|
339
|
+
status = "✗ Server unreachable"
|
|
340
|
+
elif platform_info.get("auth_valid") is False:
|
|
341
|
+
status = "✗ API key unauthorized"
|
|
342
|
+
else:
|
|
343
|
+
status = "✓ Connected"
|
|
337
344
|
click.echo(f"│ Status: {status:<48}│")
|
|
338
345
|
|
|
339
346
|
# Profile information
|
|
@@ -361,23 +368,24 @@ def info(format: str) -> None:
|
|
|
361
368
|
workspace_display = truncate(workspace)
|
|
362
369
|
click.echo(f"│ Workspace: {workspace_display:<48}│")
|
|
363
370
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
371
|
+
# Service Health section (only when services were checked)
|
|
372
|
+
services = platform_info.get("services")
|
|
373
|
+
if services:
|
|
374
|
+
click.echo("├" + "─" * content_width + "┤")
|
|
375
|
+
click.echo("│" + "Service Health".center(content_width) + "│")
|
|
376
|
+
click.echo("├" + "─" * content_width + "┤")
|
|
377
|
+
|
|
378
|
+
status_display = {
|
|
379
|
+
"ok": ("✓", "OK"),
|
|
380
|
+
"unauthorized": ("✗", "Unauthorized"),
|
|
381
|
+
"not_found": ("—", "Not available"),
|
|
382
|
+
"error": ("✗", "Error"),
|
|
383
|
+
"unreachable": ("✗", "Unreachable"),
|
|
384
|
+
}
|
|
385
|
+
for svc_name, svc_status in services.items():
|
|
386
|
+
icon, text = status_display.get(svc_status, ("?", svc_status))
|
|
387
|
+
display_name = truncate(svc_name, 29)
|
|
388
|
+
click.echo(f"│ {icon} {display_name:<30} {text:<26}│")
|
|
381
389
|
|
|
382
390
|
click.echo("└" + "─" * content_width + "┘\n")
|
|
383
391
|
|
|
@@ -8,7 +8,7 @@ import json
|
|
|
8
8
|
import os
|
|
9
9
|
import sys
|
|
10
10
|
from functools import lru_cache
|
|
11
|
-
from typing import Any, Dict
|
|
11
|
+
from typing import Any, Dict, List, Optional
|
|
12
12
|
|
|
13
13
|
import click
|
|
14
14
|
import keyring
|
|
@@ -21,6 +21,7 @@ from .utils import ExitCodes, get_ssl_verify
|
|
|
21
21
|
PLATFORM_SLE = "SLE" # SystemLink Enterprise (cloud)
|
|
22
22
|
PLATFORM_SLS = "SLS" # SystemLink Server (on-premises)
|
|
23
23
|
PLATFORM_UNKNOWN = "unknown"
|
|
24
|
+
PLATFORM_UNREACHABLE = "unreachable" # Server could not be contacted
|
|
24
25
|
|
|
25
26
|
# Feature matrix: maps features to platform availability
|
|
26
27
|
PLATFORM_FEATURES: Dict[str, Dict[str, bool]] = {
|
|
@@ -80,64 +81,21 @@ def _get_keyring_config() -> Dict[str, Any]:
|
|
|
80
81
|
def detect_platform(api_url: str, api_key: str) -> str:
|
|
81
82
|
"""Detect the SystemLink platform type by probing endpoints.
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- If matches -> SLE
|
|
88
|
-
3. Default to SLS for on-premises/custom URLs
|
|
84
|
+
Uses check_service_status to probe services and determine:
|
|
85
|
+
- Platform type (SLE vs SLS)
|
|
86
|
+
- Server reachability
|
|
87
|
+
Falls back to URL pattern matching when probe is inconclusive.
|
|
89
88
|
|
|
90
89
|
Args:
|
|
91
90
|
api_url: The SystemLink API base URL
|
|
92
91
|
api_key: The API key for authentication
|
|
93
92
|
|
|
94
93
|
Returns:
|
|
95
|
-
Platform identifier (PLATFORM_SLE, PLATFORM_SLS,
|
|
94
|
+
Platform identifier (PLATFORM_SLE, PLATFORM_SLS, PLATFORM_UNREACHABLE,
|
|
95
|
+
or PLATFORM_UNKNOWN)
|
|
96
96
|
"""
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"Content-Type": "application/json",
|
|
100
|
-
"User-Agent": "SystemLink-CLI/1.0 (cross-platform)",
|
|
101
|
-
}
|
|
102
|
-
ssl_verify = get_ssl_verify()
|
|
103
|
-
|
|
104
|
-
# Strategy 1: Probe SLE-only endpoint (Work Order service)
|
|
105
|
-
try:
|
|
106
|
-
# This endpoint only exists on SLE
|
|
107
|
-
workorder_url = f"{api_url}/niworkorder/v1/query-testplan-templates"
|
|
108
|
-
resp = requests.post(
|
|
109
|
-
workorder_url,
|
|
110
|
-
headers=headers,
|
|
111
|
-
json={"take": 1},
|
|
112
|
-
verify=ssl_verify,
|
|
113
|
-
timeout=10,
|
|
114
|
-
)
|
|
115
|
-
# If we get a 200 or 400 (bad request but endpoint exists), it's SLE
|
|
116
|
-
if resp.status_code in (200, 400):
|
|
117
|
-
return PLATFORM_SLE
|
|
118
|
-
# 404 means endpoint doesn't exist -> likely SLS
|
|
119
|
-
if resp.status_code == 404:
|
|
120
|
-
return PLATFORM_SLS
|
|
121
|
-
except requests.RequestException:
|
|
122
|
-
# Connection error - continue with other detection methods
|
|
123
|
-
pass
|
|
124
|
-
|
|
125
|
-
# Strategy 2: URL pattern matching
|
|
126
|
-
# SLE (cloud and hosted) service has specific URL patterns
|
|
127
|
-
api_url_lower = api_url.lower()
|
|
128
|
-
sle_patterns = [
|
|
129
|
-
"api.systemlink.io", # SLE production
|
|
130
|
-
"-api.lifecyclesolutions.ni.com", # SLE dev/demo with -api suffix
|
|
131
|
-
"dev-api.lifecyclesolutions",
|
|
132
|
-
"demo-api.lifecyclesolutions",
|
|
133
|
-
]
|
|
134
|
-
for pattern in sle_patterns:
|
|
135
|
-
if pattern in api_url_lower:
|
|
136
|
-
return PLATFORM_SLE
|
|
137
|
-
|
|
138
|
-
# Strategy 3: Default to SLS for on-premises deployments
|
|
139
|
-
# This includes on-prem servers that may use *.systemlink.io subdomains
|
|
140
|
-
return PLATFORM_SLS
|
|
97
|
+
status = check_service_status(api_url, api_key)
|
|
98
|
+
return status["platform"]
|
|
141
99
|
|
|
142
100
|
|
|
143
101
|
def _detect_platform_from_url(api_url: str) -> str:
|
|
@@ -274,11 +232,130 @@ def require_feature(feature_name: str) -> None:
|
|
|
274
232
|
sys.exit(ExitCodes.INVALID_INPUT)
|
|
275
233
|
|
|
276
234
|
|
|
277
|
-
|
|
235
|
+
# Services to probe during health checks.
|
|
236
|
+
# Each entry: (display_name, method, url_path)
|
|
237
|
+
SERVICE_CHECKS: List[List[str]] = [
|
|
238
|
+
["Auth", "GET", "/niauth/v1/policies"],
|
|
239
|
+
["Test Monitor", "GET", "/nitestmonitor/v2/results?take=0"],
|
|
240
|
+
["Asset Management", "POST", "/niapm/v1/query-assets"],
|
|
241
|
+
["Systems", "POST", "/nisysmgmt/v1/query-systems"],
|
|
242
|
+
["Tag", "GET", "/nitag/v2/tags?take=0"],
|
|
243
|
+
["File", "POST", "/nifile/v1/service-groups/Default/search-files"],
|
|
244
|
+
["Notebook", "POST", "/ninotebook/v1/notebook/query"],
|
|
245
|
+
["Web Application", "POST", "/niapp/v1/webapps/query"],
|
|
246
|
+
["Dynamic Form Fields", "GET", "/nidynamicformfields/v1/groups"],
|
|
247
|
+
["Work Order", "POST", "/niworkorder/v1/query-testplan-templates"],
|
|
248
|
+
]
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
252
|
+
"""Probe key SystemLink services and report their status.
|
|
253
|
+
|
|
254
|
+
Checks reachability, authorization, and availability of core services.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
api_url: The SystemLink API base URL.
|
|
258
|
+
api_key: The API key for authentication.
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
Dictionary with:
|
|
262
|
+
- server_reachable: bool - whether any service responded
|
|
263
|
+
- auth_valid: bool | None - whether the API key is authorized (None if unreachable)
|
|
264
|
+
- services: dict mapping service name to status string
|
|
265
|
+
("ok", "unauthorized", "not_found", "error", "unreachable")
|
|
266
|
+
- platform: detected platform string (PLATFORM_SLE, PLATFORM_SLS,
|
|
267
|
+
PLATFORM_UNREACHABLE)
|
|
268
|
+
"""
|
|
269
|
+
headers = {
|
|
270
|
+
"x-ni-api-key": api_key,
|
|
271
|
+
"Content-Type": "application/json",
|
|
272
|
+
"User-Agent": "SystemLink-CLI/1.0 (cross-platform)",
|
|
273
|
+
}
|
|
274
|
+
ssl_verify = get_ssl_verify()
|
|
275
|
+
|
|
276
|
+
services: Dict[str, str] = {}
|
|
277
|
+
any_responded = False
|
|
278
|
+
any_authorized = False
|
|
279
|
+
all_unauthorized = True
|
|
280
|
+
|
|
281
|
+
for display_name, method, url_path in SERVICE_CHECKS:
|
|
282
|
+
try:
|
|
283
|
+
full_url = f"{api_url}{url_path}"
|
|
284
|
+
if method == "POST":
|
|
285
|
+
resp = requests.post(
|
|
286
|
+
full_url,
|
|
287
|
+
headers=headers,
|
|
288
|
+
json={"take": 1},
|
|
289
|
+
verify=ssl_verify,
|
|
290
|
+
timeout=10,
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
resp = requests.get(
|
|
294
|
+
full_url,
|
|
295
|
+
headers=headers,
|
|
296
|
+
verify=ssl_verify,
|
|
297
|
+
timeout=10,
|
|
298
|
+
)
|
|
299
|
+
any_responded = True
|
|
300
|
+
|
|
301
|
+
if resp.status_code in (200, 400):
|
|
302
|
+
services[display_name] = "ok"
|
|
303
|
+
any_authorized = True
|
|
304
|
+
all_unauthorized = False
|
|
305
|
+
elif resp.status_code == 401:
|
|
306
|
+
services[display_name] = "unauthorized"
|
|
307
|
+
elif resp.status_code == 403:
|
|
308
|
+
services[display_name] = "unauthorized"
|
|
309
|
+
elif resp.status_code == 404:
|
|
310
|
+
services[display_name] = "not_found"
|
|
311
|
+
all_unauthorized = False
|
|
312
|
+
else:
|
|
313
|
+
services[display_name] = "error"
|
|
314
|
+
all_unauthorized = False
|
|
315
|
+
except requests.RequestException:
|
|
316
|
+
services[display_name] = "unreachable"
|
|
317
|
+
|
|
318
|
+
# Determine overall status
|
|
319
|
+
if not any_responded:
|
|
320
|
+
return {
|
|
321
|
+
"server_reachable": False,
|
|
322
|
+
"auth_valid": None,
|
|
323
|
+
"services": services,
|
|
324
|
+
"platform": PLATFORM_UNREACHABLE,
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# Determine auth status: valid if any service accepted the key
|
|
328
|
+
# If all responding services returned 401/403, the key is invalid
|
|
329
|
+
auth_valid = any_authorized if any_responded else None
|
|
330
|
+
if all_unauthorized and any_responded:
|
|
331
|
+
auth_valid = False
|
|
332
|
+
|
|
333
|
+
# Determine platform from service responses
|
|
334
|
+
workorder_status = services.get("Work Order")
|
|
335
|
+
if workorder_status in ("ok",):
|
|
336
|
+
platform = PLATFORM_SLE
|
|
337
|
+
elif workorder_status == "not_found":
|
|
338
|
+
platform = PLATFORM_SLS
|
|
339
|
+
else:
|
|
340
|
+
# Fall back to URL pattern matching
|
|
341
|
+
platform = _detect_platform_from_url(api_url)
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
"server_reachable": True,
|
|
345
|
+
"auth_valid": auth_valid,
|
|
346
|
+
"services": services,
|
|
347
|
+
"platform": platform,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def get_platform_info(skip_health: bool = False) -> Dict[str, Any]:
|
|
278
352
|
"""Get detailed information about the current platform configuration.
|
|
279
353
|
|
|
354
|
+
Args:
|
|
355
|
+
skip_health: If True, skip live service health checks.
|
|
356
|
+
|
|
280
357
|
Returns:
|
|
281
|
-
Dictionary with platform info including URL, platform type, and
|
|
358
|
+
Dictionary with platform info including URL, platform type, and services.
|
|
282
359
|
"""
|
|
283
360
|
from .utils import get_api_key, get_base_url, get_web_url
|
|
284
361
|
|
|
@@ -304,11 +381,24 @@ def get_platform_info() -> Dict[str, Any]:
|
|
|
304
381
|
|
|
305
382
|
active_profile = get_active_profile()
|
|
306
383
|
if active_profile and active_profile.platform:
|
|
307
|
-
|
|
384
|
+
stored_platform = active_profile.platform
|
|
308
385
|
else:
|
|
309
386
|
# Fall back to keyring config
|
|
310
387
|
cfg = _get_keyring_config()
|
|
311
|
-
|
|
388
|
+
stored_platform = cfg.get("platform", PLATFORM_UNKNOWN)
|
|
389
|
+
|
|
390
|
+
# Live service health check when logged in
|
|
391
|
+
server_reachable: Optional[bool] = None
|
|
392
|
+
auth_valid: Optional[bool] = None
|
|
393
|
+
services: Optional[Dict[str, str]] = None
|
|
394
|
+
platform = stored_platform
|
|
395
|
+
|
|
396
|
+
if not skip_health and logged_in and isinstance(api_url, str) and api_url != "Not configured":
|
|
397
|
+
status = check_service_status(api_url, api_key)
|
|
398
|
+
server_reachable = status["server_reachable"]
|
|
399
|
+
auth_valid = status["auth_valid"]
|
|
400
|
+
services = status["services"]
|
|
401
|
+
platform = status["platform"]
|
|
312
402
|
|
|
313
403
|
info: Dict[str, Any] = {
|
|
314
404
|
"api_url": api_url,
|
|
@@ -316,14 +406,12 @@ def get_platform_info() -> Dict[str, Any]:
|
|
|
316
406
|
"platform": platform,
|
|
317
407
|
"platform_display": _get_platform_display_name(platform),
|
|
318
408
|
"logged_in": logged_in,
|
|
409
|
+
"server_reachable": server_reachable,
|
|
410
|
+
"auth_valid": auth_valid,
|
|
319
411
|
}
|
|
320
412
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
info["features"] = {}
|
|
324
|
-
for feature, available in PLATFORM_FEATURES[platform].items():
|
|
325
|
-
display_name = FEATURE_DISPLAY_NAMES.get(feature, feature)
|
|
326
|
-
info["features"][display_name] = available
|
|
413
|
+
if services is not None:
|
|
414
|
+
info["services"] = services
|
|
327
415
|
|
|
328
416
|
return info
|
|
329
417
|
|
|
@@ -341,5 +429,6 @@ def _get_platform_display_name(platform: str) -> str:
|
|
|
341
429
|
PLATFORM_SLE: "SystemLink Enterprise",
|
|
342
430
|
PLATFORM_SLS: "SystemLink Server",
|
|
343
431
|
PLATFORM_UNKNOWN: "Unknown",
|
|
432
|
+
PLATFORM_UNREACHABLE: "Unreachable (could not connect to server)",
|
|
344
433
|
}
|
|
345
434
|
return names.get(platform, platform)
|
|
@@ -468,7 +468,7 @@ Manage named connection profiles (dev, test, prod). Credentials are stored in
|
|
|
468
468
|
```bash
|
|
469
469
|
slcli login [--profile NAME] [--url URL] [--api-key KEY] [--web-url URL] [--workspace NAME]
|
|
470
470
|
slcli logout [--profile NAME] [--all] [--force]
|
|
471
|
-
slcli info [-f json]
|
|
471
|
+
slcli info [-f json] [--skip-health] # Show active profile and service health
|
|
472
472
|
slcli completion [--shell SHELL] [--install] # Generate or install shell tab completion
|
|
473
473
|
|
|
474
474
|
slcli config list [-f json] # List all profiles
|
|
@@ -1715,14 +1715,17 @@ def register_system_commands(cli: Any) -> None:
|
|
|
1715
1715
|
}
|
|
1716
1716
|
click.echo(json.dumps(result, indent=2))
|
|
1717
1717
|
else:
|
|
1718
|
-
click.echo(
|
|
1719
|
-
click.echo("
|
|
1720
|
-
click.echo(
|
|
1721
|
-
click.echo(
|
|
1722
|
-
click.echo(f"
|
|
1723
|
-
click.echo(f"
|
|
1724
|
-
click.echo("
|
|
1725
|
-
click.echo(f"
|
|
1718
|
+
click.echo()
|
|
1719
|
+
click.echo("┌────────────────────────┐")
|
|
1720
|
+
click.echo("│ System Fleet Summary │")
|
|
1721
|
+
click.echo("├────────────────┬───────┤")
|
|
1722
|
+
click.echo(f"│ Connected │ {connected:>5} │")
|
|
1723
|
+
click.echo(f"│ Disconnected │ {disconnected:>5} │")
|
|
1724
|
+
click.echo(f"│ Virtual │ {virtual:>5} │")
|
|
1725
|
+
click.echo(f"│ Pending │ {pending:>5} │")
|
|
1726
|
+
click.echo("├────────────────┼───────┤")
|
|
1727
|
+
click.echo(f"│ Total │ {total:>5} │")
|
|
1728
|
+
click.echo("└────────────────┴───────┘")
|
|
1726
1729
|
click.echo()
|
|
1727
1730
|
|
|
1728
1731
|
except Exception as exc: # noqa: BLE001
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/demo-complete-workflow/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/demo-complete-workflow/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/exercise-7-1-test-plans/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/exercise-7-1-test-plans/config.yaml
RENAMED
|
File without changes
|
{systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/examples/spec-compliance-notebooks/config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{systemlink_cli-1.4.1 → systemlink_cli-1.4.2}/slcli/skills/slcli/references/analysis-recipes.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|