proxsync 2.0.3__tar.gz → 2.0.4__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 (73) hide show
  1. {proxsync-2.0.3/proxsync.egg-info → proxsync-2.0.4}/PKG-INFO +1 -1
  2. {proxsync-2.0.3 → proxsync-2.0.4/proxsync.egg-info}/PKG-INFO +1 -1
  3. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync.py +27 -1
  4. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/filtersets.py +25 -0
  5. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/views.py +8 -7
  6. {proxsync-2.0.3 → proxsync-2.0.4}/pyproject.toml +1 -1
  7. {proxsync-2.0.3 → proxsync-2.0.4}/MANIFEST.in +0 -0
  8. {proxsync-2.0.3 → proxsync-2.0.4}/README.md +0 -0
  9. {proxsync-2.0.3 → proxsync-2.0.4}/config.py +0 -0
  10. {proxsync-2.0.3 → proxsync-2.0.4}/pbs_sync.py +0 -0
  11. {proxsync-2.0.3 → proxsync-2.0.4}/proxsync.egg-info/SOURCES.txt +0 -0
  12. {proxsync-2.0.3 → proxsync-2.0.4}/proxsync.egg-info/dependency_links.txt +0 -0
  13. {proxsync-2.0.3 → proxsync-2.0.4}/proxsync.egg-info/requires.txt +0 -0
  14. {proxsync-2.0.3 → proxsync-2.0.4}/proxsync.egg-info/top_level.txt +0 -0
  15. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/__init__.py +0 -0
  16. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/api/__init__.py +0 -0
  17. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/api/serializers.py +0 -0
  18. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/api/urls.py +0 -0
  19. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/api/views.py +0 -0
  20. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/apps.py +0 -0
  21. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/choices.py +0 -0
  22. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/forms.py +0 -0
  23. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/management/__init__.py +0 -0
  24. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/management/commands/__init__.py +0 -0
  25. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/management/commands/pve_sync.py +0 -0
  26. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/management/commands/run_scheduled_syncs.py +0 -0
  27. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0001_initial.py +0 -0
  28. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0002_pvepluginsettings.py +0 -0
  29. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0003_netboxmodel_timestamps.py +0 -0
  30. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0004_model_verbose_names.py +0 -0
  31. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0005_align_netboxmodel_timestamps.py +0 -0
  32. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0006_pbsserverconfig_and_more.py +0 -0
  33. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0007_rename_verbose_names_to_proxmox_sync.py +0 -0
  34. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0008_pbsserverconfig_sync_schedule.py +0 -0
  35. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0009_add_every_3h_schedule.py +0 -0
  36. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0010_pvedriftevent.py +0 -0
  37. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0011_pveclusterconfig_notify_on_sync.py +0 -0
  38. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0012_pvedriftevent_tags_and_index_renames.py +0 -0
  39. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0013_pluginsettings_log_retention.py +0 -0
  40. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0014_vmprovisioninglog.py +0 -0
  41. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0015_pvevmtasklog.py +0 -0
  42. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/0016_pvevmtasklog_status_textfield.py +0 -0
  43. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/migrations/__init__.py +0 -0
  44. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/models.py +0 -0
  45. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/navigation.py +0 -0
  46. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/signals.py +0 -0
  47. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/sync/__init__.py +0 -0
  48. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/sync/config_bridge.py +0 -0
  49. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/sync/engine.py +0 -0
  50. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/tables.py +0 -0
  51. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/tasks.py +0 -0
  52. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/template_content.py +0 -0
  53. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/dashboard.html +0 -0
  54. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/inc/vm_sync_button.html +0 -0
  55. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/inc/vm_task_log_panel.html +0 -0
  56. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/pbsserverconfig.html +0 -0
  57. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/pvebackupstatus.html +0 -0
  58. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/pveclusterconfig.html +0 -0
  59. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/pvedriftevent.html +0 -0
  60. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/pvesyncjob.html +0 -0
  61. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/pvevmtasklog_list.html +0 -0
  62. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/pvewebhookevent.html +0 -0
  63. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/settings.html +0 -0
  64. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/vm_provisioning_combined.html +0 -0
  65. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templates/pve_sync/vm_provisioning_log.html +0 -0
  66. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templatetags/__init__.py +0 -0
  67. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/templatetags/pve_sync_tags.py +0 -0
  68. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/urls.py +0 -0
  69. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/utils.py +0 -0
  70. {proxsync-2.0.3 → proxsync-2.0.4}/pve_sync_plugin/view_helpers.py +0 -0
  71. {proxsync-2.0.3 → proxsync-2.0.4}/setup.cfg +0 -0
  72. {proxsync-2.0.3 → proxsync-2.0.4}/state_db.py +0 -0
  73. {proxsync-2.0.3 → proxsync-2.0.4}/tests/test_plugin_contracts.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxsync
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: NetBox plugin for Proxmox VE inventory synchronization
5
5
  Author-email: lixiujie85 <lixiujie85@gmail.com>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxsync
3
- Version: 2.0.3
3
+ Version: 2.0.4
4
4
  Summary: NetBox plugin for Proxmox VE inventory synchronization
5
5
  Author-email: lixiujie85 <lixiujie85@gmail.com>
6
6
  License-Expression: MIT
@@ -1235,6 +1235,23 @@ class OptimizedPVEToNetBoxSync:
1235
1235
  print(f"⚠️ 清理孤兒 VM 失敗: {exc}")
1236
1236
  traceback.print_exc()
1237
1237
 
1238
+ def _apply_log_retention(self):
1239
+ """依 PvePluginSettings.log_retention_days 清理過期記錄。"""
1240
+ try:
1241
+ import datetime as _dt
1242
+ from django.utils import timezone
1243
+ from pve_sync_plugin.models import PveDriftEvent, PvePluginSettings, PveVmTaskLog
1244
+ days = PvePluginSettings.load().log_retention_days
1245
+ if not days:
1246
+ return
1247
+ cutoff = timezone.now() - _dt.timedelta(days=days)
1248
+ d_del, _ = PveDriftEvent.objects.filter(created__lt=cutoff).delete()
1249
+ t_del, _ = PveVmTaskLog.objects.filter(start_time__lt=cutoff).delete()
1250
+ if d_del or t_del:
1251
+ print(f" 🧹 Log 清理:刪除 {d_del} 筆漂移事件、{t_del} 筆操作記錄(>{days} 天)")
1252
+ except Exception as exc:
1253
+ print(f" ⚠️ Log 清理失敗: {exc}")
1254
+
1238
1255
  def sync_vm_tasks(self):
1239
1256
  """從 PVE 各節點抓取 VM 操作記錄(建立/刪除/Clone/遷移)並同步到 NetBox。"""
1240
1257
  try:
@@ -1271,7 +1288,15 @@ class OptimizedPVEToNetBoxSync:
1271
1288
  # 只通知最近 2 小時內的操作(避免首次匯入時大量通知)
1272
1289
  notify_cutoff = time.time() - 7200
1273
1290
 
1274
- since_ts = int(time.time()) - 30 * 86400 # 抓最近 30 天
1291
+ # 從上次已存記錄的最新時間起算(+1 秒避免邊界重複);首次則抓 30 天
1292
+ latest = PveVmTaskLog.objects.filter(
1293
+ cluster_name=self.cluster_name
1294
+ ).order_by("-start_time").values_list("start_time", flat=True).first()
1295
+ if latest:
1296
+ import calendar
1297
+ since_ts = int(calendar.timegm(latest.utctimetuple())) + 1
1298
+ else:
1299
+ since_ts = int(time.time()) - 30 * 86400
1275
1300
  new_count = 0
1276
1301
 
1277
1302
  for node_name in self.pve_cache.get("vms_by_node", {}):
@@ -1931,6 +1956,7 @@ class OptimizedPVEToNetBoxSync:
1931
1956
  vms_success, success_count, total_count = self.sync_pve_virtual_machines(devices, cluster)
1932
1957
  print("\n同步 VM 操作記錄...")
1933
1958
  self.sync_vm_tasks()
1959
+ self._apply_log_retention()
1934
1960
  elapsed = time.time() - start_time
1935
1961
  self.stats['elapsed_time'] = elapsed
1936
1962
  self.stats['total_vms'] = total_count
@@ -10,6 +10,7 @@ from .choices import (
10
10
  SyncJobStatusChoices,
11
11
  SyncJobTriggerChoices,
12
12
  SyncScheduleChoices,
13
+ VmTaskTypeChoices,
13
14
  WebhookEventChoices,
14
15
  )
15
16
  from .models import (
@@ -18,6 +19,7 @@ from .models import (
18
19
  PveClusterConfig,
19
20
  PveDriftEvent,
20
21
  PveSyncJob,
22
+ PveVmTaskLog,
21
23
  PveWebhookEvent,
22
24
  )
23
25
 
@@ -120,6 +122,29 @@ class PveDriftEventFilterSet(NetBoxModelFilterSet):
120
122
  return queryset.filter(vm_name__icontains=value) | queryset.filter(cluster_name__icontains=value)
121
123
 
122
124
 
125
+ class PveVmTaskLogFilterSet(NetBoxModelFilterSet):
126
+ """Filter VM task logs by type, operator, cluster, and VM."""
127
+
128
+ task_type = django_filters.MultipleChoiceFilter(choices=VmTaskTypeChoices.choices)
129
+ vmid = django_filters.NumberFilter()
130
+ cluster_name = django_filters.CharFilter(lookup_expr="icontains")
131
+ operator = django_filters.CharFilter(lookup_expr="icontains")
132
+ node = django_filters.CharFilter(lookup_expr="icontains")
133
+
134
+ class Meta:
135
+ model = PveVmTaskLog
136
+ fields = ["id", "vmid", "task_type", "cluster_name", "operator", "node"]
137
+
138
+ def search(self, queryset, name, value):
139
+ if not value.strip():
140
+ return queryset
141
+ return (
142
+ queryset.filter(vm_name__icontains=value)
143
+ | queryset.filter(operator__icontains=value)
144
+ | queryset.filter(cluster_name__icontains=value)
145
+ )
146
+
147
+
123
148
  class PveBackupStatusFilterSet(NetBoxModelFilterSet):
124
149
  """Filter backup statuses by state."""
125
150
 
@@ -30,6 +30,7 @@ from .filtersets import (
30
30
  PveClusterConfigFilterSet,
31
31
  PveDriftEventFilterSet,
32
32
  PveSyncJobFilterSet,
33
+ PveVmTaskLogFilterSet,
33
34
  PveWebhookEventFilterSet,
34
35
  )
35
36
  from .forms import (
@@ -351,6 +352,7 @@ class PveDriftEventBulkDeleteView(generic.BulkDeleteView):
351
352
  class PveVmTaskLogListView(generic.ObjectListView):
352
353
  queryset = PveVmTaskLog.objects.order_by("-start_time")
353
354
  table = PveVmTaskLogTable
355
+ filterset = PveVmTaskLogFilterSet
354
356
  template_name = "pve_sync/pvevmtasklog_list.html"
355
357
 
356
358
 
@@ -666,13 +668,12 @@ class VmPlannerCheckIpApi(PermissionRequiredMixin, View):
666
668
  ip_str = str(ip_obj)
667
669
 
668
670
  # ── 1. IPAM check (authoritative) ──
669
- # An IP can be stored as any prefix length (e.g. /16, /24, /32).
670
- # Compare only the host part to find any existing allocation.
671
- ipam_entry = None
672
- for addr in NetBoxIPAddress.objects.all().values_list("address", flat=True):
673
- if str(addr.ip) == ip_str:
674
- ipam_entry = str(addr) # e.g. "172.17.202.11/16"
675
- break
671
+ # Filter by host bits using __net_host_contained: finds any /N record
672
+ # whose host address matches ip_str, without a full table scan.
673
+ ipam_qs = NetBoxIPAddress.objects.filter(
674
+ address__net_host_contained=ip_str
675
+ ).values_list("address", flat=True).first()
676
+ ipam_entry = str(ipam_qs) if ipam_qs else None
676
677
 
677
678
  if ipam_entry:
678
679
  return JsonResponse({
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "proxsync"
7
- version = "2.0.3"
7
+ version = "2.0.4"
8
8
  description = "NetBox plugin for Proxmox VE inventory synchronization"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes