osism 0.20250827.0__tar.gz → 0.20250902.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.
Files changed (152) hide show
  1. osism-0.20250902.0/AUTHORS +1 -0
  2. osism-0.20250902.0/ChangeLog +7 -0
  3. {osism-0.20250827.0/osism.egg-info → osism-0.20250902.0}/PKG-INFO +1 -1
  4. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/components/EventsFilters.tsx +2 -2
  5. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/components/EventsList.tsx +8 -8
  6. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/events/page.tsx +3 -3
  7. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/nodes/page.tsx +105 -20
  8. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/page.tsx +2 -2
  9. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/lib/hooks/useWebSocket.ts +16 -16
  10. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/package-lock.json +4 -4
  11. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/package.json +1 -1
  12. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/utils.py +10 -0
  13. {osism-0.20250827.0 → osism-0.20250902.0/osism.egg-info}/PKG-INFO +1 -1
  14. osism-0.20250902.0/osism.egg-info/pbr.json +1 -0
  15. osism-0.20250827.0/AUTHORS +0 -1
  16. osism-0.20250827.0/ChangeLog +0 -7
  17. osism-0.20250827.0/osism.egg-info/pbr.json +0 -1
  18. {osism-0.20250827.0 → osism-0.20250902.0}/.flake8 +0 -0
  19. {osism-0.20250827.0 → osism-0.20250902.0}/.github/renovate.json +0 -0
  20. {osism-0.20250827.0 → osism-0.20250902.0}/.github/workflows/publish.yml +0 -0
  21. {osism-0.20250827.0 → osism-0.20250902.0}/.hadolint.yaml +0 -0
  22. {osism-0.20250827.0 → osism-0.20250902.0}/.zuul.yaml +0 -0
  23. {osism-0.20250827.0 → osism-0.20250902.0}/Containerfile +0 -0
  24. {osism-0.20250827.0 → osism-0.20250902.0}/Dockerfile +0 -0
  25. {osism-0.20250827.0 → osism-0.20250902.0}/LICENSE +0 -0
  26. {osism-0.20250827.0 → osism-0.20250902.0}/Pipfile +0 -0
  27. {osism-0.20250827.0 → osism-0.20250902.0}/Pipfile.lock +0 -0
  28. {osism-0.20250827.0 → osism-0.20250902.0}/README.md +0 -0
  29. {osism-0.20250827.0 → osism-0.20250902.0}/files/change.sh +0 -0
  30. {osism-0.20250827.0 → osism-0.20250902.0}/files/cleanup-ansible-collections.sh +0 -0
  31. {osism-0.20250827.0 → osism-0.20250902.0}/files/clustershell/clush.conf +0 -0
  32. {osism-0.20250827.0 → osism-0.20250902.0}/files/clustershell/groups.conf +0 -0
  33. {osism-0.20250827.0 → osism-0.20250902.0}/files/data/SCS-Spec.MandatoryFlavors.verbose.yaml +0 -0
  34. {osism-0.20250827.0 → osism-0.20250902.0}/files/data/flavors.yaml +0 -0
  35. {osism-0.20250827.0 → osism-0.20250902.0}/files/netbox-manager/settings.toml +0 -0
  36. {osism-0.20250827.0 → osism-0.20250902.0}/files/redfishMockupCreate.py +0 -0
  37. {osism-0.20250827.0 → osism-0.20250902.0}/files/run-ansible-console.sh +0 -0
  38. {osism-0.20250827.0 → osism-0.20250902.0}/files/sonic/config_db.json +0 -0
  39. {osism-0.20250827.0 → osism-0.20250902.0}/files/sonic/port_config/Accton-AS4625-54T.ini +0 -0
  40. {osism-0.20250827.0 → osism-0.20250902.0}/files/sonic/port_config/Accton-AS5835-54T.ini +0 -0
  41. {osism-0.20250827.0 → osism-0.20250902.0}/files/sonic/port_config/Accton-AS5835-54X.ini +0 -0
  42. {osism-0.20250827.0 → osism-0.20250902.0}/files/sonic/port_config/Accton-AS7326-56X.ini +0 -0
  43. {osism-0.20250827.0 → osism-0.20250902.0}/files/sonic/port_config/Accton-AS7726-32X.ini +0 -0
  44. {osism-0.20250827.0 → osism-0.20250902.0}/files/sonic/port_config/Accton-AS9716-32D.ini +0 -0
  45. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/.dockerignore +0 -0
  46. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/.gitignore +0 -0
  47. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/Containerfile +0 -0
  48. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/Dockerfile +0 -0
  49. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/api/config/route.ts +0 -0
  50. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/api/health/route.ts +0 -0
  51. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/components/ConnectionStatus.tsx +0 -0
  52. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/favicon.ico +0 -0
  53. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/globals.css +0 -0
  54. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/layout.tsx +0 -0
  55. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/app/services/page.tsx +0 -0
  56. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/components.json +0 -0
  57. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/eslint.config.mjs +0 -0
  58. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/lib/api.ts +0 -0
  59. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/lib/types.ts +0 -0
  60. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/lib/utils.ts +0 -0
  61. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/next.config.ts +0 -0
  62. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/postcss.config.mjs +0 -0
  63. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/public/file.svg +0 -0
  64. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/public/globe.svg +0 -0
  65. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/public/next.svg +0 -0
  66. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/public/vercel.svg +0 -0
  67. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/public/window.svg +0 -0
  68. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/tailwind.config.ts +0 -0
  69. {osism-0.20250827.0 → osism-0.20250902.0}/frontend/tsconfig.json +0 -0
  70. {osism-0.20250827.0 → osism-0.20250902.0}/osism/__init__.py +0 -0
  71. {osism-0.20250827.0 → osism-0.20250902.0}/osism/__main__.py +0 -0
  72. {osism-0.20250827.0 → osism-0.20250902.0}/osism/api.py +0 -0
  73. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/__init__.py +0 -0
  74. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/apply.py +0 -0
  75. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/baremetal.py +0 -0
  76. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/compose.py +0 -0
  77. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/compute.py +0 -0
  78. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/configuration.py +0 -0
  79. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/console.py +0 -0
  80. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/container.py +0 -0
  81. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/get.py +0 -0
  82. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/lock.py +0 -0
  83. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/log.py +0 -0
  84. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/manage.py +0 -0
  85. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/netbox.py +0 -0
  86. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/noset.py +0 -0
  87. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/reconciler.py +0 -0
  88. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/redfish.py +0 -0
  89. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/server.py +0 -0
  90. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/service.py +0 -0
  91. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/set.py +0 -0
  92. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/sonic.py +0 -0
  93. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/status.py +0 -0
  94. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/sync.py +0 -0
  95. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/task.py +0 -0
  96. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/validate.py +0 -0
  97. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/vault.py +0 -0
  98. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/volume.py +0 -0
  99. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/wait.py +0 -0
  100. {osism-0.20250827.0 → osism-0.20250902.0}/osism/commands/worker.py +0 -0
  101. {osism-0.20250827.0 → osism-0.20250902.0}/osism/data/__init__.py +0 -0
  102. {osism-0.20250827.0 → osism-0.20250902.0}/osism/data/enums.py +0 -0
  103. {osism-0.20250827.0 → osism-0.20250902.0}/osism/data/playbooks.py +0 -0
  104. {osism-0.20250827.0 → osism-0.20250902.0}/osism/main.py +0 -0
  105. {osism-0.20250827.0 → osism-0.20250902.0}/osism/services/__init__.py +0 -0
  106. {osism-0.20250827.0 → osism-0.20250902.0}/osism/services/event_bridge.py +0 -0
  107. {osism-0.20250827.0 → osism-0.20250902.0}/osism/services/listener.py +0 -0
  108. {osism-0.20250827.0 → osism-0.20250902.0}/osism/services/websocket_manager.py +0 -0
  109. {osism-0.20250827.0 → osism-0.20250902.0}/osism/settings.py +0 -0
  110. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/__init__.py +0 -0
  111. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/ansible.py +0 -0
  112. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/ceph.py +0 -0
  113. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/__init__.py +0 -0
  114. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/config.py +0 -0
  115. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/ironic.py +0 -0
  116. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/netbox.py +0 -0
  117. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/redfish.py +0 -0
  118. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/__init__.py +0 -0
  119. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/bgp.py +0 -0
  120. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/cache.py +0 -0
  121. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/config_generator.py +0 -0
  122. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/connections.py +0 -0
  123. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/constants.py +0 -0
  124. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/device.py +0 -0
  125. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/exporter.py +0 -0
  126. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/interface.py +0 -0
  127. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor/sonic/sync.py +0 -0
  128. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/conductor.py +0 -0
  129. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/kolla.py +0 -0
  130. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/kubernetes.py +0 -0
  131. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/netbox.py +0 -0
  132. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/openstack.py +0 -0
  133. {osism-0.20250827.0 → osism-0.20250902.0}/osism/tasks/reconciler.py +0 -0
  134. {osism-0.20250827.0 → osism-0.20250902.0}/osism/utils/__init__.py +0 -0
  135. {osism-0.20250827.0 → osism-0.20250902.0}/osism/utils/ssh.py +0 -0
  136. {osism-0.20250827.0 → osism-0.20250902.0}/osism.egg-info/SOURCES.txt +0 -0
  137. {osism-0.20250827.0 → osism-0.20250902.0}/osism.egg-info/dependency_links.txt +0 -0
  138. {osism-0.20250827.0 → osism-0.20250902.0}/osism.egg-info/entry_points.txt +0 -0
  139. {osism-0.20250827.0 → osism-0.20250902.0}/osism.egg-info/not-zip-safe +0 -0
  140. {osism-0.20250827.0 → osism-0.20250902.0}/osism.egg-info/requires.txt +0 -0
  141. {osism-0.20250827.0 → osism-0.20250902.0}/osism.egg-info/top_level.txt +0 -0
  142. {osism-0.20250827.0 → osism-0.20250902.0}/playbooks/build.yml +0 -0
  143. {osism-0.20250827.0 → osism-0.20250902.0}/playbooks/pre.yml +0 -0
  144. {osism-0.20250827.0 → osism-0.20250902.0}/playbooks/test-setup.yml +0 -0
  145. {osism-0.20250827.0 → osism-0.20250902.0}/requirements.ansible.txt +0 -0
  146. {osism-0.20250827.0 → osism-0.20250902.0}/requirements.netbox-manager.txt +0 -0
  147. {osism-0.20250827.0 → osism-0.20250902.0}/requirements.openstack-flavor-manager.txt +0 -0
  148. {osism-0.20250827.0 → osism-0.20250902.0}/requirements.openstack-image-manager.txt +0 -0
  149. {osism-0.20250827.0 → osism-0.20250902.0}/requirements.txt +0 -0
  150. {osism-0.20250827.0 → osism-0.20250902.0}/requirements.yml +0 -0
  151. {osism-0.20250827.0 → osism-0.20250902.0}/setup.cfg +0 -0
  152. {osism-0.20250827.0 → osism-0.20250902.0}/setup.py +0 -0
@@ -0,0 +1 @@
1
+ Christian Berendt <berendt@osism.tech>
@@ -0,0 +1,7 @@
1
+ CHANGES
2
+ =======
3
+
4
+ v0.20250902.0
5
+ -------------
6
+
7
+ * fix: Add missing check\_task\_lock\_and\_exit function to conductor utils (#1704)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osism
3
- Version: 0.20250827.0
3
+ Version: 0.20250902.0
4
4
  Summary: OSISM manager interface
5
5
  Home-page: https://github.com/osism/python-osism
6
6
  Author: OSISM GmbH
@@ -12,7 +12,7 @@ interface EventsFiltersProps {
12
12
  const BAREMETAL_EVENT_TYPES = [
13
13
  "baremetal.node.power_set.end",
14
14
  "baremetal.node.provision_set.start",
15
- "baremetal.node.provision_set.end",
15
+ "baremetal.node.provision_set.end",
16
16
  "baremetal.node.provision_set.success",
17
17
  "baremetal.node.power_state_corrected.success",
18
18
  "baremetal.node.maintenance_set.end",
@@ -49,7 +49,7 @@ export default function EventsFilters({ onFiltersChange, className = "" }: Event
49
49
  }, [onFiltersChange]);
50
50
 
51
51
  const toggleEventType = useCallback((eventType: string) => {
52
- setSelectedEventTypes(prev =>
52
+ setSelectedEventTypes(prev =>
53
53
  prev.includes(eventType)
54
54
  ? prev.filter(t => t !== eventType)
55
55
  : [...prev, eventType]
@@ -2,11 +2,11 @@
2
2
 
3
3
  import { useMemo, useState } from "react";
4
4
  import { format } from "date-fns";
5
- import {
6
- Server,
7
- Power,
8
- Settings,
9
- Trash2,
5
+ import {
6
+ Server,
7
+ Power,
8
+ Settings,
9
+ Trash2,
10
10
  Plus,
11
11
  CheckCircle,
12
12
  XCircle,
@@ -52,17 +52,17 @@ const formatEventData = (event: OpenStackEvent) => {
52
52
  if (event.data.service_type === "baremetal" && "ironic_object" in event.data) {
53
53
  const baremetalEvent = event as BaremetalEvent;
54
54
  const ironicData = baremetalEvent.data.ironic_object?.data;
55
-
55
+
56
56
  if (!ironicData) return null;
57
57
 
58
58
  const details = [];
59
59
  if (ironicData.power_state) details.push(`Power: ${ironicData.power_state}`);
60
60
  if (ironicData.provision_state) details.push(`Provision: ${ironicData.provision_state}`);
61
61
  if (ironicData.maintenance !== undefined) details.push(`Maintenance: ${ironicData.maintenance ? "Yes" : "No"}`);
62
-
62
+
63
63
  return details.join(" | ");
64
64
  }
65
-
65
+
66
66
  return null;
67
67
  };
68
68
 
@@ -109,7 +109,7 @@ export default function EventsPage() {
109
109
  Real-time Baremetal events from OpenStack Ironic
110
110
  </p>
111
111
  </div>
112
-
112
+
113
113
  {/* Connection Status */}
114
114
  <ConnectionStatus status={connectionStatus} />
115
115
  </div>
@@ -131,7 +131,7 @@ export default function EventsPage() {
131
131
  )}
132
132
  </div>
133
133
  </div>
134
-
134
+
135
135
  <EventsFilters onFiltersChange={handleFiltersChange} />
136
136
  </div>
137
137
 
@@ -144,7 +144,7 @@ export default function EventsPage() {
144
144
  <RefreshCw className="h-4 w-4 mr-2" />
145
145
  Reconnect
146
146
  </button>
147
-
147
+
148
148
  <button
149
149
  onClick={handleClearEvents}
150
150
  className="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useState } from "react";
4
4
  import { useQuery } from "@tanstack/react-query";
5
- import { RefreshCw, Search, AlertCircle, ArrowUpDown } from "lucide-react";
5
+ import { RefreshCw, Search, AlertCircle, ArrowUpDown, ChevronLeft, ChevronRight } from "lucide-react";
6
6
  import api from "@/lib/api";
7
7
  import { BaremetalNode } from "@/lib/types";
8
8
 
@@ -12,6 +12,8 @@ export default function NodesPage() {
12
12
  const [filterPowerState, setFilterPowerState] = useState("all");
13
13
  const [filterMaintenance, setFilterMaintenance] = useState("all");
14
14
  const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
15
+ const [currentPage, setCurrentPage] = useState(1);
16
+ const itemsPerPage = 10;
15
17
 
16
18
  const { data, isLoading, error, refetch, isRefetching } = useQuery({
17
19
  queryKey: ["baremetal-nodes"],
@@ -21,14 +23,14 @@ export default function NodesPage() {
21
23
 
22
24
  const filteredAndSortedNodes = data?.nodes
23
25
  .filter((node: BaremetalNode) => {
24
- const matchesSearch = searchTerm === "" ||
26
+ const matchesSearch = searchTerm === "" ||
25
27
  (node.name && node.name.toLowerCase().includes(searchTerm.toLowerCase())) ||
26
28
  (node.uuid && node.uuid.toLowerCase().includes(searchTerm.toLowerCase()));
27
-
28
- const matchesProvisionState = filterProvisionState === "all" ||
29
+
30
+ const matchesProvisionState = filterProvisionState === "all" ||
29
31
  node.provision_state === filterProvisionState;
30
-
31
- const matchesPowerState = filterPowerState === "all" ||
32
+
33
+ const matchesPowerState = filterPowerState === "all" ||
32
34
  node.power_state === filterPowerState;
33
35
 
34
36
  const matchesMaintenance = filterMaintenance === "all" ||
@@ -40,7 +42,7 @@ export default function NodesPage() {
40
42
  .sort((a: BaremetalNode, b: BaremetalNode) => {
41
43
  const nameA = a.name || a.uuid || '';
42
44
  const nameB = b.name || b.uuid || '';
43
-
45
+
44
46
  if (sortDirection === "asc") {
45
47
  return nameA.localeCompare(nameB);
46
48
  } else {
@@ -48,14 +50,25 @@ export default function NodesPage() {
48
50
  }
49
51
  });
50
52
 
53
+ const totalFilteredNodes = filteredAndSortedNodes?.length || 0;
54
+ const totalPages = Math.ceil(totalFilteredNodes / itemsPerPage);
55
+ const startIndex = (currentPage - 1) * itemsPerPage;
56
+ const endIndex = startIndex + itemsPerPage;
57
+ const paginatedNodes = filteredAndSortedNodes?.slice(startIndex, endIndex);
58
+
59
+ // Reset to first page when filters change
60
+ const resetToFirstPage = () => {
61
+ setCurrentPage(1);
62
+ };
63
+
51
64
  const toggleSortDirection = () => {
52
65
  setSortDirection(prev => prev === "asc" ? "desc" : "asc");
53
66
  };
54
67
 
55
- const uniqueProvisionStates = data ?
68
+ const uniqueProvisionStates = data ?
56
69
  [...new Set(data.nodes.map(n => n.provision_state).filter((state): state is string => Boolean(state)))] : [];
57
-
58
- const uniquePowerStates = data ?
70
+
71
+ const uniquePowerStates = data ?
59
72
  [...new Set(data.nodes.map(n => n.power_state).filter((state): state is string => Boolean(state)))] : [];
60
73
 
61
74
  return (
@@ -95,7 +108,10 @@ export default function NodesPage() {
95
108
  className="focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 pr-3 py-2 sm:text-sm border-gray-300 rounded-md"
96
109
  placeholder="Name or UUID..."
97
110
  value={searchTerm}
98
- onChange={(e) => setSearchTerm(e.target.value)}
111
+ onChange={(e) => {
112
+ setSearchTerm(e.target.value);
113
+ resetToFirstPage();
114
+ }}
99
115
  />
100
116
  </div>
101
117
  </div>
@@ -109,7 +125,10 @@ export default function NodesPage() {
109
125
  name="provision-state"
110
126
  className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
111
127
  value={filterProvisionState}
112
- onChange={(e) => setFilterProvisionState(e.target.value)}
128
+ onChange={(e) => {
129
+ setFilterProvisionState(e.target.value);
130
+ resetToFirstPage();
131
+ }}
113
132
  >
114
133
  <option value="all">All</option>
115
134
  {uniqueProvisionStates.map((state, index) => (
@@ -127,7 +146,10 @@ export default function NodesPage() {
127
146
  name="power-state"
128
147
  className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
129
148
  value={filterPowerState}
130
- onChange={(e) => setFilterPowerState(e.target.value)}
149
+ onChange={(e) => {
150
+ setFilterPowerState(e.target.value);
151
+ resetToFirstPage();
152
+ }}
131
153
  >
132
154
  <option value="all">All</option>
133
155
  {uniquePowerStates.map((state, index) => (
@@ -145,7 +167,10 @@ export default function NodesPage() {
145
167
  name="maintenance-state"
146
168
  className="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"
147
169
  value={filterMaintenance}
148
- onChange={(e) => setFilterMaintenance(e.target.value)}
170
+ onChange={(e) => {
171
+ setFilterMaintenance(e.target.value);
172
+ resetToFirstPage();
173
+ }}
149
174
  >
150
175
  <option value="all">All</option>
151
176
  <option value="active">Active</option>
@@ -185,7 +210,7 @@ export default function NodesPage() {
185
210
  </div>
186
211
  </div>
187
212
  </div>
188
- ) : filteredAndSortedNodes && filteredAndSortedNodes.length > 0 ? (
213
+ ) : paginatedNodes && paginatedNodes.length > 0 ? (
189
214
  <div className="bg-white shadow overflow-hidden sm:rounded-md">
190
215
  <div className="px-4 py-3 bg-gray-50 border-b border-gray-200">
191
216
  <div className="flex items-center">
@@ -202,7 +227,7 @@ export default function NodesPage() {
202
227
  </div>
203
228
  </div>
204
229
  <ul className="divide-y divide-gray-200">
205
- {filteredAndSortedNodes.map((node: BaremetalNode, index) => (
230
+ {paginatedNodes.map((node: BaremetalNode, index) => (
206
231
  <li key={node.uuid || `node-${index}`}>
207
232
  <div className="px-4 py-4 sm:px-6">
208
233
  <div className="flex items-center justify-between">
@@ -220,7 +245,7 @@ export default function NodesPage() {
220
245
  </div>
221
246
  <div className="flex items-center justify-end gap-2">
222
247
  <span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
223
- node.power_state === "power on"
248
+ node.power_state === "power on"
224
249
  ? "bg-green-100 text-green-800"
225
250
  : "bg-gray-100 text-gray-800"
226
251
  }`}>
@@ -283,9 +308,69 @@ export default function NodesPage() {
283
308
  </div>
284
309
  )}
285
310
 
286
- {data && (
287
- <div className="mt-4 text-sm text-gray-600">
288
- Showing {filteredAndSortedNodes?.length || 0} of {data.count} nodes
311
+ {data && totalFilteredNodes > 0 && (
312
+ <div className="mt-6">
313
+ <div className="flex items-center justify-between mb-4">
314
+ <div className="text-sm text-gray-600">
315
+ Showing {startIndex + 1} to {Math.min(endIndex, totalFilteredNodes)} of {totalFilteredNodes} filtered nodes
316
+ {totalFilteredNodes !== data.count && (
317
+ <span className="text-gray-500"> ({data.count} total)</span>
318
+ )}
319
+ </div>
320
+ {totalPages > 1 && (
321
+ <div className="flex items-center space-x-2">
322
+ <button
323
+ onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
324
+ disabled={currentPage === 1}
325
+ className="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
326
+ >
327
+ <ChevronLeft className="h-4 w-4 mr-1" />
328
+ Previous
329
+ </button>
330
+
331
+ <div className="flex items-center space-x-1">
332
+ {Array.from({ length: totalPages }, (_, i) => i + 1)
333
+ .filter(page => {
334
+ // Show first page, last page, current page, and pages around current
335
+ return page === 1 ||
336
+ page === totalPages ||
337
+ Math.abs(page - currentPage) <= 1;
338
+ })
339
+ .map((page, index, array) => {
340
+ // Add ellipsis if there's a gap
341
+ const showEllipsisBefore = index > 0 && page - array[index - 1] > 1;
342
+ return (
343
+ <div key={page} className="flex items-center">
344
+ {showEllipsisBefore && (
345
+ <span className="px-2 py-1 text-gray-500">...</span>
346
+ )}
347
+ <button
348
+ onClick={() => setCurrentPage(page)}
349
+ className={`px-3 py-2 text-sm font-medium rounded-md ${
350
+ currentPage === page
351
+ ? "bg-blue-500 text-white"
352
+ : "text-gray-700 bg-white border border-gray-300 hover:bg-gray-50"
353
+ } focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500`}
354
+ >
355
+ {page}
356
+ </button>
357
+ </div>
358
+ );
359
+ })
360
+ }
361
+ </div>
362
+
363
+ <button
364
+ onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
365
+ disabled={currentPage === totalPages}
366
+ className="inline-flex items-center px-3 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
367
+ >
368
+ Next
369
+ <ChevronRight className="h-4 w-4 ml-1" />
370
+ </button>
371
+ </div>
372
+ )}
373
+ </div>
289
374
  </div>
290
375
  )}
291
376
  </div>
@@ -28,7 +28,7 @@ export default function Home() {
28
28
  loading: nodesLoading,
29
29
  },
30
30
  {
31
- id: "active-nodes",
31
+ id: "active-nodes",
32
32
  name: "Active Nodes",
33
33
  value: nodesData?.nodes.filter(n => n.provision_state === "active").length || 0,
34
34
  icon: Activity,
@@ -37,7 +37,7 @@ export default function Home() {
37
37
  },
38
38
  {
39
39
  id: "services",
40
- name: "Services",
40
+ name: "Services",
41
41
  value: "N/A",
42
42
  icon: Settings,
43
43
  href: "/services",
@@ -58,7 +58,7 @@ export const useWebSocket = (
58
58
  }
59
59
 
60
60
  isConnectingRef.current = true;
61
-
61
+
62
62
  try {
63
63
  // Convert http/https URL to ws/wss for WebSocket
64
64
  const wsUrl = url.replace(/^http/, 'ws');
@@ -69,25 +69,25 @@ export const useWebSocket = (
69
69
  console.log('WebSocket connected:', wsUrl);
70
70
  isConnectingRef.current = false;
71
71
  reconnectAttemptsRef.current = 0;
72
-
72
+
73
73
  setConnectionStatus({
74
74
  connected: true,
75
75
  lastConnected: new Date()
76
76
  });
77
-
77
+
78
78
  onConnect?.();
79
79
  };
80
80
 
81
81
  websocket.onmessage = (event) => {
82
82
  try {
83
83
  const data = JSON.parse(event.data);
84
-
84
+
85
85
  // Handle filter acknowledgment messages
86
86
  if (data.type === 'filter_update') {
87
87
  console.log('Filters updated:', data);
88
88
  return;
89
89
  }
90
-
90
+
91
91
  // Handle OpenStack events
92
92
  if (data.event_type) {
93
93
  addEvent(data as OpenStackEvent);
@@ -101,19 +101,19 @@ export const useWebSocket = (
101
101
  console.log('WebSocket disconnected:', event.code, event.reason);
102
102
  isConnectingRef.current = false;
103
103
  wsRef.current = null;
104
-
104
+
105
105
  setConnectionStatus({
106
106
  connected: false,
107
107
  error: event.reason || 'Connection closed'
108
108
  });
109
-
109
+
110
110
  onDisconnect?.();
111
-
111
+
112
112
  // Attempt reconnection if not manually closed
113
113
  if (event.code !== 1000 && reconnectAttemptsRef.current < reconnectAttempts) {
114
114
  reconnectAttemptsRef.current++;
115
115
  console.log(`Attempting reconnect ${reconnectAttemptsRef.current}/${reconnectAttempts}`);
116
-
116
+
117
117
  reconnectTimeoutRef.current = setTimeout(() => {
118
118
  connect();
119
119
  }, reconnectInterval);
@@ -124,21 +124,21 @@ export const useWebSocket = (
124
124
  console.error('WebSocket error:', error);
125
125
  console.error('WebSocket URL was:', wsUrl);
126
126
  isConnectingRef.current = false;
127
-
127
+
128
128
  setConnectionStatus({
129
129
  connected: false,
130
130
  error: 'Connection error - check console for details'
131
131
  });
132
-
132
+
133
133
  onError?.(error);
134
134
  };
135
135
 
136
136
  wsRef.current = websocket;
137
-
137
+
138
138
  } catch (error) {
139
139
  console.error('Failed to create WebSocket connection:', error);
140
140
  isConnectingRef.current = false;
141
-
141
+
142
142
  setConnectionStatus({
143
143
  connected: false,
144
144
  error: 'Failed to connect'
@@ -150,15 +150,15 @@ export const useWebSocket = (
150
150
  if (reconnectTimeoutRef.current) {
151
151
  clearTimeout(reconnectTimeoutRef.current);
152
152
  }
153
-
153
+
154
154
  if (wsRef.current) {
155
155
  wsRef.current.close(1000, 'Manual disconnect');
156
156
  wsRef.current = null;
157
157
  }
158
-
158
+
159
159
  reconnectAttemptsRef.current = reconnectAttempts; // Prevent reconnection
160
160
  isConnectingRef.current = false;
161
-
161
+
162
162
  setConnectionStatus({
163
163
  connected: false
164
164
  });
@@ -13,7 +13,7 @@
13
13
  "axios": "^1.11.0",
14
14
  "clsx": "^2.1.1",
15
15
  "date-fns": "^4.1.0",
16
- "lucide-react": "^0.541.0",
16
+ "lucide-react": "^0.542.0",
17
17
  "next": "15.5.0",
18
18
  "react": "19.1.1",
19
19
  "react-dom": "19.1.1",
@@ -4664,9 +4664,9 @@
4664
4664
  }
4665
4665
  },
4666
4666
  "node_modules/lucide-react": {
4667
- "version": "0.541.0",
4668
- "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.541.0.tgz",
4669
- "integrity": "sha512-s0Vircsu5WaGv2KoJZ5+SoxiAJ3UXV5KqEM3eIFDHaHkcLIFdIWgXtZ412+Gh02UsdS7Was+jvEpBvPCWQISlg==",
4667
+ "version": "0.542.0",
4668
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz",
4669
+ "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==",
4670
4670
  "license": "ISC",
4671
4671
  "peerDependencies": {
4672
4672
  "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -14,7 +14,7 @@
14
14
  "axios": "^1.11.0",
15
15
  "clsx": "^2.1.1",
16
16
  "date-fns": "^4.1.0",
17
- "lucide-react": "^0.541.0",
17
+ "lucide-react": "^0.542.0",
18
18
  "next": "15.5.0",
19
19
  "react": "19.1.1",
20
20
  "react-dom": "19.1.1",
@@ -232,3 +232,13 @@ def _get_conductor_redfish_address(device):
232
232
  logger.warning(f"Could not get conductor Redfish address: {exc}")
233
233
 
234
234
  return None
235
+
236
+
237
+ def check_task_lock_and_exit():
238
+ """
239
+ Check if tasks are locked and exit with error message if they are.
240
+ Used by commands that should not run when tasks are locked.
241
+
242
+ This is a convenience wrapper around the main utils function.
243
+ """
244
+ return utils.check_task_lock_and_exit()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: osism
3
- Version: 0.20250827.0
3
+ Version: 0.20250902.0
4
4
  Summary: OSISM manager interface
5
5
  Home-page: https://github.com/osism/python-osism
6
6
  Author: OSISM GmbH
@@ -0,0 +1 @@
1
+ {"git_version": "8ffeee4", "is_release": false}
@@ -1 +0,0 @@
1
- renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@@ -1,7 +0,0 @@
1
- CHANGES
2
- =======
3
-
4
- v0.20250827.0
5
- -------------
6
-
7
- * chore(deps): update react monorepo to v19.1.12 (#1679)
@@ -1 +0,0 @@
1
- {"git_version": "bbad13f", "is_release": false}
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