kctl-glpi 0.2.0__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.
- kctl_glpi-0.2.0/.gitignore +33 -0
- kctl_glpi-0.2.0/PKG-INFO +28 -0
- kctl_glpi-0.2.0/README.md +3 -0
- kctl_glpi-0.2.0/pyproject.toml +60 -0
- kctl_glpi-0.2.0/src/kctl_glpi/__init__.py +1 -0
- kctl_glpi-0.2.0/src/kctl_glpi/__main__.py +3 -0
- kctl_glpi-0.2.0/src/kctl_glpi/cli.py +138 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/__init__.py +0 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/assets.py +220 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/config_cmd.py +516 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/dashboard.py +130 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/doctor_cmd.py +140 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/entities.py +144 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/health.py +102 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/plugins_cmd.py +108 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/search_cmd.py +123 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/skill_cmd.py +74 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/tickets.py +224 -0
- kctl_glpi-0.2.0/src/kctl_glpi/commands/users.py +186 -0
- kctl_glpi-0.2.0/src/kctl_glpi/core/__init__.py +0 -0
- kctl_glpi-0.2.0/src/kctl_glpi/core/callbacks.py +32 -0
- kctl_glpi-0.2.0/src/kctl_glpi/core/client.py +265 -0
- kctl_glpi-0.2.0/src/kctl_glpi/core/config.py +131 -0
- kctl_glpi-0.2.0/src/kctl_glpi/core/exceptions.py +39 -0
- kctl_glpi-0.2.0/src/kctl_glpi/core/plugins.py +13 -0
- kctl_glpi-0.2.0/tests/__init__.py +0 -0
- kctl_glpi-0.2.0/tests/test_cli.py +56 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
*.egg
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
.eggs/
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# IDE
|
|
15
|
+
.idea/
|
|
16
|
+
.vscode/
|
|
17
|
+
*.swp
|
|
18
|
+
*.swo
|
|
19
|
+
|
|
20
|
+
# Testing
|
|
21
|
+
.pytest_cache/
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
# Environment
|
|
32
|
+
.env
|
|
33
|
+
.env.local
|
kctl_glpi-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kctl-glpi
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Kodemeio GLPI CLI - manage GLPI IT Service Management platform
|
|
5
|
+
Author-email: Kodemeio <dev@kodeme.io>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: asset-management,cli,glpi,helpdesk,itsm,kodemeio
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: System Administrators
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: System :: Systems Administration
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Requires-Dist: httpx>=0.28.0
|
|
17
|
+
Requires-Dist: kctl-lib>=0.7.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: mypy>=1.14.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-httpx>=0.35.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.3.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.9.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: types-pyyaml>=6.0.0; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# kctl-glpi
|
|
27
|
+
|
|
28
|
+
GLPI IT asset management CLI for Kodemeio infrastructure.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kctl-glpi"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Kodemeio GLPI CLI - manage GLPI IT Service Management platform"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
authors = [{ name = "Kodemeio", email = "dev@kodeme.io" }]
|
|
13
|
+
keywords = ["glpi", "itsm", "helpdesk", "asset-management", "cli", "kodemeio"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: System Administrators",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: System :: Systems Administration",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"kctl-lib>=0.7.0",
|
|
25
|
+
"httpx>=0.28.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=8.3.0",
|
|
31
|
+
"pytest-httpx>=0.35.0",
|
|
32
|
+
"ruff>=0.9.0",
|
|
33
|
+
"mypy>=1.14.0",
|
|
34
|
+
"types-PyYAML>=6.0.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
kctl-glpi = "kctl_glpi.cli:_run"
|
|
39
|
+
|
|
40
|
+
[tool.uv.sources]
|
|
41
|
+
kctl-lib = { workspace = true }
|
|
42
|
+
|
|
43
|
+
[project.entry-points."kctl_glpi.plugins"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/kctl_glpi"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
target-version = "py312"
|
|
50
|
+
line-length = 120
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
select = ["E", "F", "I", "W", "UP", "B", "SIM"]
|
|
54
|
+
|
|
55
|
+
[tool.mypy]
|
|
56
|
+
python_version = "3.12"
|
|
57
|
+
strict = true
|
|
58
|
+
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Main CLI entry point for kctl-glpi."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from kctl_lib import KctlError, handle_cli_error
|
|
9
|
+
from kctl_lib.self_update import notify_if_outdated
|
|
10
|
+
from kctl_lib.tui import add_tui_command
|
|
11
|
+
|
|
12
|
+
from kctl_glpi import __version__
|
|
13
|
+
from kctl_glpi.commands.assets import app as assets_app
|
|
14
|
+
from kctl_glpi.commands.config_cmd import app as config_app
|
|
15
|
+
from kctl_glpi.commands.dashboard import app as dashboard_app
|
|
16
|
+
from kctl_glpi.commands.doctor_cmd import app as doctor_app
|
|
17
|
+
from kctl_glpi.commands.entities import app as entities_app
|
|
18
|
+
from kctl_glpi.commands.health import app as health_app
|
|
19
|
+
from kctl_glpi.commands.plugins_cmd import app as plugins_app
|
|
20
|
+
from kctl_glpi.commands.search_cmd import app as search_app
|
|
21
|
+
from kctl_glpi.commands.skill_cmd import app as skill_app
|
|
22
|
+
from kctl_glpi.commands.tickets import app as tickets_app
|
|
23
|
+
from kctl_glpi.commands.users import app as users_app
|
|
24
|
+
from kctl_glpi.core.callbacks import AppContext
|
|
25
|
+
from kctl_glpi.core.plugins import discover_and_load_plugins
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def version_callback(value: bool) -> None:
|
|
29
|
+
if value:
|
|
30
|
+
typer.echo(f"kctl-glpi {__version__}")
|
|
31
|
+
raise typer.Exit()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
app = typer.Typer(
|
|
35
|
+
name="kctl-glpi",
|
|
36
|
+
help="Kodemeio GLPI CLI - manage your GLPI IT Service Management platform.",
|
|
37
|
+
no_args_is_help=True,
|
|
38
|
+
rich_markup_mode="rich",
|
|
39
|
+
pretty_exceptions_enable=False,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.callback()
|
|
44
|
+
def main(
|
|
45
|
+
ctx: typer.Context,
|
|
46
|
+
json_output: Annotated[bool, typer.Option("--json", help="Output as JSON")] = False,
|
|
47
|
+
quiet: Annotated[bool, typer.Option("--quiet", "-q", help="Suppress info messages")] = False,
|
|
48
|
+
profile: Annotated[str | None, typer.Option("--profile", "-p", help="Config profile name")] = None,
|
|
49
|
+
format: Annotated[str, typer.Option("--format", "-f", help="Output format: pretty, json, csv, yaml")] = "pretty",
|
|
50
|
+
no_header: Annotated[bool, typer.Option("--no-header", help="Omit header row in CSV output")] = False,
|
|
51
|
+
url: Annotated[str | None, typer.Option("--url", help="API URL override")] = None,
|
|
52
|
+
app_token: Annotated[str | None, typer.Option("--app-token", help="App-Token override")] = None,
|
|
53
|
+
user_token: Annotated[str | None, typer.Option("--user-token", help="User token override")] = None,
|
|
54
|
+
version: Annotated[
|
|
55
|
+
bool, typer.Option("--version", "-V", callback=version_callback, is_eager=True, help="Show version")
|
|
56
|
+
] = False,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Kodemeio GLPI CLI."""
|
|
59
|
+
ctx.ensure_object(dict)
|
|
60
|
+
ctx.obj = AppContext(
|
|
61
|
+
json_mode=json_output,
|
|
62
|
+
quiet=quiet,
|
|
63
|
+
profile=profile,
|
|
64
|
+
format=format,
|
|
65
|
+
no_header=no_header,
|
|
66
|
+
url_override=url,
|
|
67
|
+
app_token_override=app_token,
|
|
68
|
+
user_token_override=user_token,
|
|
69
|
+
)
|
|
70
|
+
notify_if_outdated(ctx.obj.output, "kctl-glpi", __version__)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# Register all command groups
|
|
74
|
+
app.add_typer(tickets_app, name="tickets")
|
|
75
|
+
app.add_typer(assets_app, name="assets")
|
|
76
|
+
app.add_typer(users_app, name="users")
|
|
77
|
+
app.add_typer(entities_app, name="entities")
|
|
78
|
+
app.add_typer(search_app, name="search")
|
|
79
|
+
app.add_typer(plugins_app, name="plugins")
|
|
80
|
+
app.add_typer(health_app, name="health")
|
|
81
|
+
app.add_typer(dashboard_app, name="dashboard")
|
|
82
|
+
app.add_typer(config_app, name="config")
|
|
83
|
+
app.add_typer(doctor_app, name="doctor")
|
|
84
|
+
app.add_typer(skill_app, name="skill", hidden=True)
|
|
85
|
+
|
|
86
|
+
# Load third-party plugins via entry points
|
|
87
|
+
discover_and_load_plugins(app)
|
|
88
|
+
add_tui_command(app, service_key="glpi", version=__version__)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@app.command("self-update")
|
|
92
|
+
def self_update_cmd(ctx: typer.Context) -> None:
|
|
93
|
+
"""Check for updates and upgrade kctl-glpi."""
|
|
94
|
+
actx = ctx.obj
|
|
95
|
+
out = actx.output
|
|
96
|
+
|
|
97
|
+
from kctl_lib.self_update import check_update
|
|
98
|
+
from kctl_lib.self_update import update as do_update
|
|
99
|
+
|
|
100
|
+
latest = check_update("kctl-glpi", __version__)
|
|
101
|
+
if latest:
|
|
102
|
+
out.info(f"Updating to {latest}...")
|
|
103
|
+
do_update("kctl-glpi")
|
|
104
|
+
out.success(f"Updated to {latest}")
|
|
105
|
+
else:
|
|
106
|
+
out.success("Already up to date")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command()
|
|
110
|
+
def completions(
|
|
111
|
+
shell: Annotated[str, typer.Argument(help="Shell type: zsh, bash, fish")] = "zsh",
|
|
112
|
+
install: Annotated[bool, typer.Option("--install", help="Install completions")] = False,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Generate or install shell completions."""
|
|
115
|
+
from kctl_lib.completions import get_completion_script, install_completions
|
|
116
|
+
|
|
117
|
+
if install:
|
|
118
|
+
path = install_completions("kctl-glpi", shell)
|
|
119
|
+
if path:
|
|
120
|
+
typer.echo(f"Completions installed to {path}")
|
|
121
|
+
else:
|
|
122
|
+
typer.echo(f"Could not install completions for {shell}", err=True)
|
|
123
|
+
raise typer.Exit(code=1)
|
|
124
|
+
else:
|
|
125
|
+
script = get_completion_script("kctl-glpi", shell)
|
|
126
|
+
typer.echo(script)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _run() -> None:
|
|
130
|
+
"""Entry point with error handling."""
|
|
131
|
+
try:
|
|
132
|
+
app()
|
|
133
|
+
except KctlError as e:
|
|
134
|
+
handle_cli_error(e)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
_run()
|
|
File without changes
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""Asset management commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from kctl_lib.exceptions import KctlError
|
|
9
|
+
|
|
10
|
+
from kctl_glpi.core.callbacks import AppContext
|
|
11
|
+
|
|
12
|
+
app = typer.Typer(help="Manage GLPI assets (computers).")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@app.command("list")
|
|
16
|
+
def list_assets(
|
|
17
|
+
ctx: typer.Context,
|
|
18
|
+
limit: Annotated[int, typer.Option("--limit", "-l", help="Max results")] = 50,
|
|
19
|
+
name_filter: Annotated[str | None, typer.Option("--name", "-n", help="Filter by name (contains)")] = None,
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List computers/assets with optional filters."""
|
|
22
|
+
actx: AppContext = ctx.obj
|
|
23
|
+
c, out = actx.client, actx.output
|
|
24
|
+
|
|
25
|
+
criteria: list[dict] = []
|
|
26
|
+
if name_filter:
|
|
27
|
+
criteria.append({"field": 1, "searchtype": "contains", "value": name_filter})
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
result = c.search("Computer", criteria=criteria or None, params={"range": f"0-{limit - 1}"})
|
|
31
|
+
assets = result.get("data", [])
|
|
32
|
+
except KctlError as e:
|
|
33
|
+
out.error(f"Failed to list assets: {e}")
|
|
34
|
+
raise typer.Exit(1) from e
|
|
35
|
+
|
|
36
|
+
if not assets:
|
|
37
|
+
out.info("No assets found")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
rows: list[list[str]] = []
|
|
41
|
+
for a in assets:
|
|
42
|
+
rows.append(
|
|
43
|
+
[
|
|
44
|
+
str(a.get("2", a.get("id", ""))), # ID
|
|
45
|
+
str(a.get("1", "")), # Name
|
|
46
|
+
str(a.get("4", "")), # Type
|
|
47
|
+
str(a.get("5", "")), # Serial
|
|
48
|
+
str(a.get("31", "")), # Status
|
|
49
|
+
str(a.get("70", "")), # User
|
|
50
|
+
]
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
out.table(
|
|
54
|
+
f"Computers ({len(assets)})",
|
|
55
|
+
[("ID", "cyan"), ("Name", ""), ("Type", ""), ("Serial", "dim"), ("Status", ""), ("User", "")],
|
|
56
|
+
rows,
|
|
57
|
+
data_for_json=assets,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command()
|
|
62
|
+
def get(
|
|
63
|
+
ctx: typer.Context,
|
|
64
|
+
asset_id: Annotated[int, typer.Argument(help="Computer/asset ID")],
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Get asset details by ID."""
|
|
67
|
+
actx: AppContext = ctx.obj
|
|
68
|
+
c, out = actx.client, actx.output
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
asset = c.get_item("Computer", asset_id)
|
|
72
|
+
except KctlError as e:
|
|
73
|
+
out.error(f"Failed to get asset {asset_id}: {e}")
|
|
74
|
+
raise typer.Exit(1) from e
|
|
75
|
+
|
|
76
|
+
if not asset:
|
|
77
|
+
out.error(f"Asset {asset_id} not found")
|
|
78
|
+
raise typer.Exit(1)
|
|
79
|
+
|
|
80
|
+
sections: list[tuple[str, list[tuple[str, str]]]] = [
|
|
81
|
+
(
|
|
82
|
+
f"Computer #{asset.get('id', asset_id)}",
|
|
83
|
+
[
|
|
84
|
+
("Name", asset.get("name", "")),
|
|
85
|
+
("Serial", asset.get("serial", "")),
|
|
86
|
+
("OTH Number", asset.get("otherserial", "")),
|
|
87
|
+
("Type", str(asset.get("computertypes_id", ""))),
|
|
88
|
+
("Model", str(asset.get("computermodels_id", ""))),
|
|
89
|
+
("Manufacturer", str(asset.get("manufacturers_id", ""))),
|
|
90
|
+
("Status", str(asset.get("states_id", ""))),
|
|
91
|
+
("Location", str(asset.get("locations_id", ""))),
|
|
92
|
+
("User", str(asset.get("users_id", ""))),
|
|
93
|
+
("Group", str(asset.get("groups_id", ""))),
|
|
94
|
+
("Entity", str(asset.get("entities_id", ""))),
|
|
95
|
+
("Date Created", asset.get("date_creation", "")),
|
|
96
|
+
("Date Modified", asset.get("date_mod", "")),
|
|
97
|
+
("Comment", (asset.get("comment", "") or "")[:300]),
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
out.detail(f"Computer #{asset_id}", sections, data_for_json=asset)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@app.command()
|
|
106
|
+
def create(
|
|
107
|
+
ctx: typer.Context,
|
|
108
|
+
name: Annotated[str, typer.Option("--name", "-n", help="Computer name")],
|
|
109
|
+
serial: Annotated[str | None, typer.Option("--serial", "-s", help="Serial number")] = None,
|
|
110
|
+
computer_type: Annotated[int | None, typer.Option("--type", help="Computer type ID")] = None,
|
|
111
|
+
status: Annotated[int | None, typer.Option("--status", help="Status ID")] = None,
|
|
112
|
+
location: Annotated[int | None, typer.Option("--location", help="Location ID")] = None,
|
|
113
|
+
user: Annotated[int | None, typer.Option("--user", help="User ID")] = None,
|
|
114
|
+
comment: Annotated[str | None, typer.Option("--comment", help="Comment")] = None,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""Create a new computer/asset."""
|
|
117
|
+
actx: AppContext = ctx.obj
|
|
118
|
+
c, out = actx.client, actx.output
|
|
119
|
+
|
|
120
|
+
data: dict = {"name": name}
|
|
121
|
+
if serial is not None:
|
|
122
|
+
data["serial"] = serial
|
|
123
|
+
if computer_type is not None:
|
|
124
|
+
data["computertypes_id"] = computer_type
|
|
125
|
+
if status is not None:
|
|
126
|
+
data["states_id"] = status
|
|
127
|
+
if location is not None:
|
|
128
|
+
data["locations_id"] = location
|
|
129
|
+
if user is not None:
|
|
130
|
+
data["users_id"] = user
|
|
131
|
+
if comment is not None:
|
|
132
|
+
data["comment"] = comment
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
result = c.create_item("Computer", data)
|
|
136
|
+
out.success(f"Computer created: {result}")
|
|
137
|
+
except KctlError as e:
|
|
138
|
+
out.error(f"Failed to create computer: {e}")
|
|
139
|
+
raise typer.Exit(1) from e
|
|
140
|
+
|
|
141
|
+
if out.json_mode:
|
|
142
|
+
out.raw_json(result)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command()
|
|
146
|
+
def update(
|
|
147
|
+
ctx: typer.Context,
|
|
148
|
+
asset_id: Annotated[int, typer.Argument(help="Computer/asset ID")],
|
|
149
|
+
name: Annotated[str | None, typer.Option("--name", "-n", help="New name")] = None,
|
|
150
|
+
serial: Annotated[str | None, typer.Option("--serial", "-s", help="New serial number")] = None,
|
|
151
|
+
status: Annotated[int | None, typer.Option("--status", help="New status ID")] = None,
|
|
152
|
+
location: Annotated[int | None, typer.Option("--location", help="New location ID")] = None,
|
|
153
|
+
user: Annotated[int | None, typer.Option("--user", help="New user ID")] = None,
|
|
154
|
+
comment: Annotated[str | None, typer.Option("--comment", help="New comment")] = None,
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Update a computer/asset."""
|
|
157
|
+
actx: AppContext = ctx.obj
|
|
158
|
+
c, out = actx.client, actx.output
|
|
159
|
+
|
|
160
|
+
data: dict = {}
|
|
161
|
+
if name is not None:
|
|
162
|
+
data["name"] = name
|
|
163
|
+
if serial is not None:
|
|
164
|
+
data["serial"] = serial
|
|
165
|
+
if status is not None:
|
|
166
|
+
data["states_id"] = status
|
|
167
|
+
if location is not None:
|
|
168
|
+
data["locations_id"] = location
|
|
169
|
+
if user is not None:
|
|
170
|
+
data["users_id"] = user
|
|
171
|
+
if comment is not None:
|
|
172
|
+
data["comment"] = comment
|
|
173
|
+
|
|
174
|
+
if not data:
|
|
175
|
+
out.error("No fields to update. Use --name, --serial, --status, etc.")
|
|
176
|
+
raise typer.Exit(1)
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
result = c.update_item("Computer", asset_id, data)
|
|
180
|
+
out.success(f"Computer {asset_id} updated")
|
|
181
|
+
except KctlError as e:
|
|
182
|
+
out.error(f"Failed to update computer {asset_id}: {e}")
|
|
183
|
+
raise typer.Exit(1) from e
|
|
184
|
+
|
|
185
|
+
if out.json_mode:
|
|
186
|
+
out.raw_json(result)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@app.command()
|
|
190
|
+
def types(ctx: typer.Context) -> None:
|
|
191
|
+
"""List available computer types."""
|
|
192
|
+
actx: AppContext = ctx.obj
|
|
193
|
+
c, out = actx.client, actx.output
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
items = c.get_items("ComputerType")
|
|
197
|
+
except KctlError as e:
|
|
198
|
+
out.error(f"Failed to list computer types: {e}")
|
|
199
|
+
raise typer.Exit(1) from e
|
|
200
|
+
|
|
201
|
+
if not items:
|
|
202
|
+
out.info("No computer types found")
|
|
203
|
+
return
|
|
204
|
+
|
|
205
|
+
rows: list[list[str]] = []
|
|
206
|
+
for item in items:
|
|
207
|
+
rows.append(
|
|
208
|
+
[
|
|
209
|
+
str(item.get("id", "")),
|
|
210
|
+
item.get("name", ""),
|
|
211
|
+
item.get("comment", "") or "",
|
|
212
|
+
]
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
out.table(
|
|
216
|
+
"Computer Types",
|
|
217
|
+
[("ID", "cyan"), ("Name", ""), ("Comment", "dim")],
|
|
218
|
+
rows,
|
|
219
|
+
data_for_json=items,
|
|
220
|
+
)
|