systemlink-cli 1.4.2__tar.gz → 1.4.3__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.2 → systemlink_cli-1.4.3}/PKG-INFO +1 -1
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/pyproject.toml +1 -1
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/_version.py +1 -1
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/config_click.py +8 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/file_click.py +136 -11
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/main.py +10 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/platform.py +103 -1
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/LICENSE +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/dff-editor/editor.js +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/dff-editor/index.html +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/__init__.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/__main__.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/asset_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/cli_formatters.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/cli_utils.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/comment_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/completion_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/config.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/dff_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/dff_decorators.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/example_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/example_loader.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/example_provisioner.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/README.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/_schema/schema-v1.0.json +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/demo-complete-workflow/README.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/demo-complete-workflow/config.yaml +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/demo-test-plans/README.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/demo-test-plans/config.yaml +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/exercise-5-1-parametric-insights/README.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/exercise-5-1-parametric-insights/config.yaml +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/exercise-7-1-test-plans/README.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/exercise-7-1-test-plans/config.yaml +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/spec-compliance-notebooks/README.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/spec-compliance-notebooks/config.yaml +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/feed_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/function_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/function_templates.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/mcp_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/mcp_server.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/notebook_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/policy_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/policy_utils.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/profiles.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/response_handlers.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/routine_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skill_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skills/slcli/SKILL.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skills/slcli/references/analysis-recipes.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skills/slcli/references/filtering.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skills/systemlink-webapp/SKILL.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skills/systemlink-webapp/references/deployment.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skills/systemlink-webapp/references/nimble-angular.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/skills/systemlink-webapp/references/systemlink-services.md +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/ssl_trust.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/system_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/table_utils.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/tag_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/templates_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/testmonitor_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/universal_handlers.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/user_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/utils.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/web_editor.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/webapp_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/workflow_preview.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/workflows_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/workitem_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/workspace_click.py +0 -0
- {systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/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.3"
|
|
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" }]
|
|
@@ -64,6 +64,7 @@ def _add_profile_impl(
|
|
|
64
64
|
elif not url.startswith("https://"):
|
|
65
65
|
click.echo("⚠️ Warning: Adding HTTPS protocol to URL.")
|
|
66
66
|
url = f"https://{url}"
|
|
67
|
+
url = url.rstrip("/")
|
|
67
68
|
|
|
68
69
|
# Get API key - either from flag or prompt
|
|
69
70
|
if not api_key:
|
|
@@ -86,6 +87,7 @@ def _add_profile_impl(
|
|
|
86
87
|
elif not web_url.startswith("https://"):
|
|
87
88
|
click.echo("⚠️ Warning: Adding HTTPS protocol to web URL.")
|
|
88
89
|
web_url = f"https://{web_url}"
|
|
90
|
+
web_url = web_url.rstrip("/")
|
|
89
91
|
|
|
90
92
|
# Detect platform type and check service status
|
|
91
93
|
click.echo("Checking server connectivity and services...")
|
|
@@ -113,6 +115,12 @@ def _add_profile_impl(
|
|
|
113
115
|
elif status["auth_valid"] is True:
|
|
114
116
|
click.echo(" API key: ✓ Authorized")
|
|
115
117
|
|
|
118
|
+
if status.get("elasticsearch_available") is False:
|
|
119
|
+
click.echo(" File query: query-files-linq (Elasticsearch unavailable)")
|
|
120
|
+
click.echo(
|
|
121
|
+
" 'slcli file list' will fall back automatically; 'slcli file query' requires search-files."
|
|
122
|
+
)
|
|
123
|
+
|
|
116
124
|
# Report individual service status
|
|
117
125
|
services = status.get("services", {})
|
|
118
126
|
problem_services = [
|
|
@@ -10,9 +10,10 @@ from typing import Any, Dict, List, Optional
|
|
|
10
10
|
|
|
11
11
|
import click
|
|
12
12
|
import questionary
|
|
13
|
+
import requests as requests_lib
|
|
13
14
|
|
|
14
15
|
from .cli_utils import validate_output_format
|
|
15
|
-
from .universal_handlers import UniversalResponseHandler
|
|
16
|
+
from .universal_handlers import FilteredResponse, UniversalResponseHandler
|
|
16
17
|
from .utils import (
|
|
17
18
|
ExitCodes,
|
|
18
19
|
format_success,
|
|
@@ -29,6 +30,120 @@ def _get_file_service_url() -> str:
|
|
|
29
30
|
return f"{get_base_url()}/nifile/v1/service-groups/Default"
|
|
30
31
|
|
|
31
32
|
|
|
33
|
+
def _get_search_files_url() -> str:
|
|
34
|
+
"""Get the search-files endpoint URL."""
|
|
35
|
+
return f"{_get_file_service_url()}/search-files"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _is_search_files_unavailable(exc: requests_lib.HTTPError) -> bool:
|
|
39
|
+
"""Return whether the search-files endpoint is unavailable."""
|
|
40
|
+
return exc.response is not None and exc.response.status_code in (404, 501)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _exit_search_files_required() -> None:
|
|
44
|
+
"""Exit with a user-facing message when search-files is unavailable."""
|
|
45
|
+
click.echo(
|
|
46
|
+
"✗ File query requires the search-files endpoint, but Elasticsearch is not available.",
|
|
47
|
+
err=True,
|
|
48
|
+
)
|
|
49
|
+
click.echo(
|
|
50
|
+
" Use 'slcli file list' for automatic fallback to query-files-linq.",
|
|
51
|
+
err=True,
|
|
52
|
+
)
|
|
53
|
+
sys.exit(ExitCodes.INVALID_INPUT)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _search_files_with_fallback(
|
|
57
|
+
payload: Dict[str, Any],
|
|
58
|
+
workspace_id: Optional[str],
|
|
59
|
+
name_filter: Optional[str],
|
|
60
|
+
id_filter: Optional[str],
|
|
61
|
+
) -> Any:
|
|
62
|
+
"""Try search-files endpoint, fall back to query-files-linq if unavailable.
|
|
63
|
+
|
|
64
|
+
The search-files endpoint requires additional software (DataFinder) to be
|
|
65
|
+
installed. If it is not available (HTTP 404), we fall back to query-files-linq.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
payload: The search-files request payload
|
|
69
|
+
workspace_id: Optional workspace ID filter
|
|
70
|
+
name_filter: Optional name filter string
|
|
71
|
+
id_filter: Optional comma-separated file IDs
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Response object (real or FilteredResponse)
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
return make_api_request(
|
|
78
|
+
"POST", _get_search_files_url(), payload=payload, handle_errors=False
|
|
79
|
+
)
|
|
80
|
+
except requests_lib.HTTPError as exc:
|
|
81
|
+
if _is_search_files_unavailable(exc):
|
|
82
|
+
return _query_files_linq_fallback(
|
|
83
|
+
take=payload.get("take", 25),
|
|
84
|
+
workspace_id=workspace_id,
|
|
85
|
+
name_filter=name_filter,
|
|
86
|
+
id_filter=id_filter,
|
|
87
|
+
)
|
|
88
|
+
raise
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _query_files_linq_fallback(
|
|
92
|
+
take: int,
|
|
93
|
+
workspace_id: Optional[str],
|
|
94
|
+
name_filter: Optional[str],
|
|
95
|
+
id_filter: Optional[str],
|
|
96
|
+
) -> Any:
|
|
97
|
+
"""List files using query-files-linq as a fallback when search-files is unavailable.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
take: Maximum number of files to return
|
|
101
|
+
workspace_id: Optional workspace ID filter
|
|
102
|
+
name_filter: Optional name filter string (applied client-side)
|
|
103
|
+
id_filter: Optional comma-separated file IDs
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Response or FilteredResponse with availableFiles data
|
|
107
|
+
"""
|
|
108
|
+
base_url = _get_file_service_url()
|
|
109
|
+
url = f"{base_url}/query-files-linq"
|
|
110
|
+
|
|
111
|
+
# Build LINQ-style filter
|
|
112
|
+
filter_parts: List[str] = []
|
|
113
|
+
|
|
114
|
+
if id_filter:
|
|
115
|
+
ids = [fid.strip() for fid in id_filter.split(",")]
|
|
116
|
+
id_conditions = [f'id = "{fid}"' for fid in ids]
|
|
117
|
+
filter_parts.append("(" + " or ".join(id_conditions) + ")")
|
|
118
|
+
|
|
119
|
+
linq_payload: Dict[str, Any] = {}
|
|
120
|
+
|
|
121
|
+
# Request more if we need to filter client-side by name
|
|
122
|
+
if name_filter:
|
|
123
|
+
linq_payload["take"] = max(take * 4, 1000)
|
|
124
|
+
else:
|
|
125
|
+
linq_payload["take"] = take
|
|
126
|
+
|
|
127
|
+
if filter_parts:
|
|
128
|
+
linq_payload["filter"] = " and ".join(filter_parts)
|
|
129
|
+
|
|
130
|
+
# Workspace filtering via query parameter
|
|
131
|
+
if workspace_id:
|
|
132
|
+
url += f"?workspace={workspace_id}"
|
|
133
|
+
|
|
134
|
+
resp = make_api_request("POST", url, payload=linq_payload)
|
|
135
|
+
|
|
136
|
+
# Client-side name filtering (query-files-linq doesn't support name wildcards)
|
|
137
|
+
if name_filter:
|
|
138
|
+
data = resp.json()
|
|
139
|
+
files = data.get("availableFiles", [])
|
|
140
|
+
needle = name_filter.lower()
|
|
141
|
+
filtered = [f for f in files if needle in _get_file_name(f).lower()][:take]
|
|
142
|
+
return FilteredResponse({"availableFiles": filtered})
|
|
143
|
+
|
|
144
|
+
return resp
|
|
145
|
+
|
|
146
|
+
|
|
32
147
|
def _format_file_size(size_bytes: Optional[int]) -> str:
|
|
33
148
|
"""Format file size in human-readable format.
|
|
34
149
|
|
|
@@ -178,9 +293,6 @@ def register_file_commands(cli: Any) -> None:
|
|
|
178
293
|
"""
|
|
179
294
|
format_output = validate_output_format(format)
|
|
180
295
|
|
|
181
|
-
# Use search-files endpoint for better performance
|
|
182
|
-
url = f"{_get_file_service_url()}/search-files"
|
|
183
|
-
|
|
184
296
|
try:
|
|
185
297
|
# Resolve workspace name to ID if needed
|
|
186
298
|
workspace_id = None
|
|
@@ -189,7 +301,7 @@ def register_file_commands(cli: Any) -> None:
|
|
|
189
301
|
workspace_map = get_workspace_map()
|
|
190
302
|
workspace_id = resolve_workspace_filter(workspace, workspace_map)
|
|
191
303
|
|
|
192
|
-
# Build search filter
|
|
304
|
+
# Build search filter for search-files endpoint
|
|
193
305
|
filter_parts = []
|
|
194
306
|
|
|
195
307
|
if workspace_id:
|
|
@@ -221,7 +333,13 @@ def register_file_commands(cli: Any) -> None:
|
|
|
221
333
|
if filter_parts:
|
|
222
334
|
payload["filter"] = " AND ".join(filter_parts)
|
|
223
335
|
|
|
224
|
-
|
|
336
|
+
# Try search-files, fall back to query-files-linq if unavailable
|
|
337
|
+
resp = _search_files_with_fallback(
|
|
338
|
+
payload=payload,
|
|
339
|
+
workspace_id=workspace_id,
|
|
340
|
+
name_filter=name_filter,
|
|
341
|
+
id_filter=id_filter,
|
|
342
|
+
)
|
|
225
343
|
|
|
226
344
|
def file_formatter(file_item: dict) -> list:
|
|
227
345
|
name = _get_file_name(file_item)
|
|
@@ -567,11 +685,8 @@ def register_file_commands(cli: Any) -> None:
|
|
|
567
685
|
"""
|
|
568
686
|
format_output = validate_output_format(format)
|
|
569
687
|
|
|
570
|
-
# Use search-files endpoint for better performance
|
|
571
|
-
url = f"{_get_file_service_url()}/search-files"
|
|
572
|
-
|
|
573
688
|
try:
|
|
574
|
-
# Build request body
|
|
689
|
+
# Build request body for search-files
|
|
575
690
|
query_body: Dict[str, Any] = {
|
|
576
691
|
"take": take if format_output.lower() == "json" else (take if take != 25 else 1000),
|
|
577
692
|
"orderByDescending": descending,
|
|
@@ -584,6 +699,7 @@ def register_file_commands(cli: Any) -> None:
|
|
|
584
699
|
filter_parts.append(filter_query)
|
|
585
700
|
|
|
586
701
|
# Resolve workspace name to ID if needed
|
|
702
|
+
workspace_id = None
|
|
587
703
|
workspace = get_effective_workspace(workspace)
|
|
588
704
|
if workspace:
|
|
589
705
|
workspace_map = get_workspace_map()
|
|
@@ -598,7 +714,12 @@ def register_file_commands(cli: Any) -> None:
|
|
|
598
714
|
else:
|
|
599
715
|
query_body["orderBy"] = "updated"
|
|
600
716
|
|
|
601
|
-
resp = make_api_request(
|
|
717
|
+
resp = make_api_request(
|
|
718
|
+
"POST",
|
|
719
|
+
_get_search_files_url(),
|
|
720
|
+
payload=query_body,
|
|
721
|
+
handle_errors=False,
|
|
722
|
+
)
|
|
602
723
|
|
|
603
724
|
def file_formatter(file_item: dict) -> list:
|
|
604
725
|
name = _get_file_name(file_item)
|
|
@@ -620,6 +741,10 @@ def register_file_commands(cli: Any) -> None:
|
|
|
620
741
|
page_size=take,
|
|
621
742
|
)
|
|
622
743
|
|
|
744
|
+
except requests_lib.HTTPError as exc:
|
|
745
|
+
if _is_search_files_unavailable(exc):
|
|
746
|
+
_exit_search_files_required()
|
|
747
|
+
handle_api_error(exc)
|
|
623
748
|
except Exception as exc:
|
|
624
749
|
handle_api_error(exc)
|
|
625
750
|
|
|
@@ -368,6 +368,15 @@ def info(format: str, skip_health: bool) -> None:
|
|
|
368
368
|
workspace_display = truncate(workspace)
|
|
369
369
|
click.echo(f"│ Workspace: {workspace_display:<48}│")
|
|
370
370
|
|
|
371
|
+
file_query_endpoint = platform_info.get("file_query_endpoint")
|
|
372
|
+
if file_query_endpoint:
|
|
373
|
+
if platform_info.get("elasticsearch_available") is False:
|
|
374
|
+
file_query_display = f"{file_query_endpoint} (Elasticsearch unavailable)"
|
|
375
|
+
else:
|
|
376
|
+
file_query_display = str(file_query_endpoint)
|
|
377
|
+
file_query_display = truncate(file_query_display)
|
|
378
|
+
click.echo(f"│ File Query:{file_query_display:<48}│")
|
|
379
|
+
|
|
371
380
|
# Service Health section (only when services were checked)
|
|
372
381
|
services = platform_info.get("services")
|
|
373
382
|
if services:
|
|
@@ -377,6 +386,7 @@ def info(format: str, skip_health: bool) -> None:
|
|
|
377
386
|
|
|
378
387
|
status_display = {
|
|
379
388
|
"ok": ("✓", "OK"),
|
|
389
|
+
"fallback": ("!", "Fallback (no Elasticsearch)"),
|
|
380
390
|
"unauthorized": ("✗", "Unauthorized"),
|
|
381
391
|
"not_found": ("—", "Not available"),
|
|
382
392
|
"error": ("✗", "Error"),
|
|
@@ -56,6 +56,9 @@ FEATURE_DISPLAY_NAMES: Dict[str, str] = {
|
|
|
56
56
|
"webapp": "Web Applications",
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
FILE_SEARCH_PATH = "/nifile/v1/service-groups/Default/search-files"
|
|
60
|
+
FILE_QUERY_LINQ_PATH = "/nifile/v1/service-groups/Default/query-files-linq"
|
|
61
|
+
|
|
59
62
|
|
|
60
63
|
def _get_keyring_config() -> Dict[str, Any]:
|
|
61
64
|
"""Attempt to read a single JSON config entry from keyring.
|
|
@@ -240,7 +243,7 @@ SERVICE_CHECKS: List[List[str]] = [
|
|
|
240
243
|
["Asset Management", "POST", "/niapm/v1/query-assets"],
|
|
241
244
|
["Systems", "POST", "/nisysmgmt/v1/query-systems"],
|
|
242
245
|
["Tag", "GET", "/nitag/v2/tags?take=0"],
|
|
243
|
-
["File", "POST",
|
|
246
|
+
["File", "POST", FILE_SEARCH_PATH],
|
|
244
247
|
["Notebook", "POST", "/ninotebook/v1/notebook/query"],
|
|
245
248
|
["Web Application", "POST", "/niapp/v1/webapps/query"],
|
|
246
249
|
["Dynamic Form Fields", "GET", "/nidynamicformfields/v1/groups"],
|
|
@@ -248,6 +251,87 @@ SERVICE_CHECKS: List[List[str]] = [
|
|
|
248
251
|
]
|
|
249
252
|
|
|
250
253
|
|
|
254
|
+
def get_file_query_capability(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
255
|
+
"""Determine which file query endpoint is available for this server."""
|
|
256
|
+
headers = {
|
|
257
|
+
"x-ni-api-key": api_key,
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
"User-Agent": "SystemLink-CLI/1.0 (cross-platform)",
|
|
260
|
+
}
|
|
261
|
+
ssl_verify = get_ssl_verify()
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
search_resp = requests.post(
|
|
265
|
+
f"{api_url}{FILE_SEARCH_PATH}",
|
|
266
|
+
headers=headers,
|
|
267
|
+
json={"take": 1},
|
|
268
|
+
verify=ssl_verify,
|
|
269
|
+
timeout=10,
|
|
270
|
+
)
|
|
271
|
+
if search_resp.status_code in (200, 400):
|
|
272
|
+
return {
|
|
273
|
+
"status": "ok",
|
|
274
|
+
"file_query_endpoint": "search-files",
|
|
275
|
+
"elasticsearch_available": True,
|
|
276
|
+
}
|
|
277
|
+
if search_resp.status_code in (401, 403):
|
|
278
|
+
return {
|
|
279
|
+
"status": "unauthorized",
|
|
280
|
+
"file_query_endpoint": "search-files",
|
|
281
|
+
"elasticsearch_available": True,
|
|
282
|
+
}
|
|
283
|
+
if search_resp.status_code not in (404, 501):
|
|
284
|
+
return {
|
|
285
|
+
"status": "error" if search_resp.status_code >= 500 else "not_found",
|
|
286
|
+
"file_query_endpoint": None,
|
|
287
|
+
"elasticsearch_available": True,
|
|
288
|
+
}
|
|
289
|
+
except requests.RequestException:
|
|
290
|
+
return {
|
|
291
|
+
"status": "unreachable",
|
|
292
|
+
"file_query_endpoint": None,
|
|
293
|
+
"elasticsearch_available": None,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
fallback_resp = requests.post(
|
|
298
|
+
f"{api_url}{FILE_QUERY_LINQ_PATH}",
|
|
299
|
+
headers=headers,
|
|
300
|
+
json={"take": 1},
|
|
301
|
+
verify=ssl_verify,
|
|
302
|
+
timeout=10,
|
|
303
|
+
)
|
|
304
|
+
if fallback_resp.status_code in (200, 400):
|
|
305
|
+
return {
|
|
306
|
+
"status": "fallback",
|
|
307
|
+
"file_query_endpoint": "query-files-linq",
|
|
308
|
+
"elasticsearch_available": False,
|
|
309
|
+
}
|
|
310
|
+
if fallback_resp.status_code in (401, 403):
|
|
311
|
+
return {
|
|
312
|
+
"status": "unauthorized",
|
|
313
|
+
"file_query_endpoint": "query-files-linq",
|
|
314
|
+
"elasticsearch_available": False,
|
|
315
|
+
}
|
|
316
|
+
if fallback_resp.status_code in (404, 501):
|
|
317
|
+
return {
|
|
318
|
+
"status": "not_found",
|
|
319
|
+
"file_query_endpoint": None,
|
|
320
|
+
"elasticsearch_available": False,
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
"status": "error",
|
|
324
|
+
"file_query_endpoint": None,
|
|
325
|
+
"elasticsearch_available": False,
|
|
326
|
+
}
|
|
327
|
+
except requests.RequestException:
|
|
328
|
+
return {
|
|
329
|
+
"status": "unreachable",
|
|
330
|
+
"file_query_endpoint": None,
|
|
331
|
+
"elasticsearch_available": False,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
251
335
|
def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
252
336
|
"""Probe key SystemLink services and report their status.
|
|
253
337
|
|
|
@@ -263,6 +347,8 @@ def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
263
347
|
- auth_valid: bool | None - whether the API key is authorized (None if unreachable)
|
|
264
348
|
- services: dict mapping service name to status string
|
|
265
349
|
("ok", "unauthorized", "not_found", "error", "unreachable")
|
|
350
|
+
- file_query_endpoint: selected file query endpoint, if available
|
|
351
|
+
- elasticsearch_available: bool | None - whether search-files is available
|
|
266
352
|
- platform: detected platform string (PLATFORM_SLE, PLATFORM_SLS,
|
|
267
353
|
PLATFORM_UNREACHABLE)
|
|
268
354
|
"""
|
|
@@ -321,6 +407,8 @@ def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
321
407
|
"server_reachable": False,
|
|
322
408
|
"auth_valid": None,
|
|
323
409
|
"services": services,
|
|
410
|
+
"file_query_endpoint": None,
|
|
411
|
+
"elasticsearch_available": None,
|
|
324
412
|
"platform": PLATFORM_UNREACHABLE,
|
|
325
413
|
}
|
|
326
414
|
|
|
@@ -340,10 +428,15 @@ def check_service_status(api_url: str, api_key: str) -> Dict[str, Any]:
|
|
|
340
428
|
# Fall back to URL pattern matching
|
|
341
429
|
platform = _detect_platform_from_url(api_url)
|
|
342
430
|
|
|
431
|
+
file_capability = get_file_query_capability(api_url, api_key)
|
|
432
|
+
services["File"] = file_capability["status"]
|
|
433
|
+
|
|
343
434
|
return {
|
|
344
435
|
"server_reachable": True,
|
|
345
436
|
"auth_valid": auth_valid,
|
|
346
437
|
"services": services,
|
|
438
|
+
"file_query_endpoint": file_capability["file_query_endpoint"],
|
|
439
|
+
"elasticsearch_available": file_capability["elasticsearch_available"],
|
|
347
440
|
"platform": platform,
|
|
348
441
|
}
|
|
349
442
|
|
|
@@ -392,6 +485,8 @@ def get_platform_info(skip_health: bool = False) -> Dict[str, Any]:
|
|
|
392
485
|
auth_valid: Optional[bool] = None
|
|
393
486
|
services: Optional[Dict[str, str]] = None
|
|
394
487
|
platform = stored_platform
|
|
488
|
+
file_query_endpoint: Optional[str] = None
|
|
489
|
+
elasticsearch_available: Optional[bool] = None
|
|
395
490
|
|
|
396
491
|
if not skip_health and logged_in and isinstance(api_url, str) and api_url != "Not configured":
|
|
397
492
|
status = check_service_status(api_url, api_key)
|
|
@@ -399,6 +494,8 @@ def get_platform_info(skip_health: bool = False) -> Dict[str, Any]:
|
|
|
399
494
|
auth_valid = status["auth_valid"]
|
|
400
495
|
services = status["services"]
|
|
401
496
|
platform = status["platform"]
|
|
497
|
+
file_query_endpoint = status.get("file_query_endpoint")
|
|
498
|
+
elasticsearch_available = status.get("elasticsearch_available")
|
|
402
499
|
|
|
403
500
|
info: Dict[str, Any] = {
|
|
404
501
|
"api_url": api_url,
|
|
@@ -410,6 +507,11 @@ def get_platform_info(skip_health: bool = False) -> Dict[str, Any]:
|
|
|
410
507
|
"auth_valid": auth_valid,
|
|
411
508
|
}
|
|
412
509
|
|
|
510
|
+
if file_query_endpoint is not None:
|
|
511
|
+
info["file_query_endpoint"] = file_query_endpoint
|
|
512
|
+
if elasticsearch_available is not None:
|
|
513
|
+
info["elasticsearch_available"] = elasticsearch_available
|
|
514
|
+
|
|
413
515
|
if services is not None:
|
|
414
516
|
info["services"] = services
|
|
415
517
|
|
|
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.2 → systemlink_cli-1.4.3}/slcli/examples/demo-complete-workflow/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/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.2 → systemlink_cli-1.4.3}/slcli/examples/exercise-7-1-test-plans/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/exercise-7-1-test-plans/config.yaml
RENAMED
|
File without changes
|
{systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/slcli/examples/spec-compliance-notebooks/README.md
RENAMED
|
File without changes
|
{systemlink_cli-1.4.2 → systemlink_cli-1.4.3}/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.2 → systemlink_cli-1.4.3}/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
|
|
File without changes
|