proxsync 2.0.0__py3-none-any.whl
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.
- config.py +337 -0
- pbs_sync.py +244 -0
- proxsync-2.0.0.dist-info/METADATA +468 -0
- proxsync-2.0.0.dist-info/RECORD +59 -0
- proxsync-2.0.0.dist-info/WHEEL +5 -0
- proxsync-2.0.0.dist-info/top_level.txt +5 -0
- pve_sync.py +1804 -0
- pve_sync_plugin/__init__.py +46 -0
- pve_sync_plugin/api/__init__.py +1 -0
- pve_sync_plugin/api/serializers.py +161 -0
- pve_sync_plugin/api/urls.py +27 -0
- pve_sync_plugin/api/views.py +94 -0
- pve_sync_plugin/apps.py +5 -0
- pve_sync_plugin/choices.py +57 -0
- pve_sync_plugin/filtersets.py +137 -0
- pve_sync_plugin/forms.py +219 -0
- pve_sync_plugin/management/__init__.py +1 -0
- pve_sync_plugin/management/commands/__init__.py +1 -0
- pve_sync_plugin/management/commands/pve_sync.py +182 -0
- pve_sync_plugin/management/commands/run_scheduled_syncs.py +109 -0
- pve_sync_plugin/migrations/0001_initial.py +234 -0
- pve_sync_plugin/migrations/0002_pvepluginsettings.py +42 -0
- pve_sync_plugin/migrations/0003_netboxmodel_timestamps.py +148 -0
- pve_sync_plugin/migrations/0004_model_verbose_names.py +34 -0
- pve_sync_plugin/migrations/0005_align_netboxmodel_timestamps.py +111 -0
- pve_sync_plugin/migrations/0006_pbsserverconfig_and_more.py +124 -0
- pve_sync_plugin/migrations/0007_rename_verbose_names_to_proxmox_sync.py +26 -0
- pve_sync_plugin/migrations/0008_pbsserverconfig_sync_schedule.py +26 -0
- pve_sync_plugin/migrations/0009_add_every_3h_schedule.py +39 -0
- pve_sync_plugin/migrations/0010_pvedriftevent.py +62 -0
- pve_sync_plugin/migrations/0011_pveclusterconfig_notify_on_sync.py +19 -0
- pve_sync_plugin/migrations/0012_pvedriftevent_tags_and_index_renames.py +39 -0
- pve_sync_plugin/migrations/0013_pluginsettings_log_retention.py +19 -0
- pve_sync_plugin/migrations/__init__.py +1 -0
- pve_sync_plugin/models.py +461 -0
- pve_sync_plugin/navigation.py +94 -0
- pve_sync_plugin/signals.py +24 -0
- pve_sync_plugin/sync/__init__.py +8 -0
- pve_sync_plugin/sync/config_bridge.py +221 -0
- pve_sync_plugin/sync/engine.py +50 -0
- pve_sync_plugin/tables.py +278 -0
- pve_sync_plugin/tasks.py +440 -0
- pve_sync_plugin/template_content.py +44 -0
- pve_sync_plugin/templates/pve_sync/dashboard.html +400 -0
- pve_sync_plugin/templates/pve_sync/inc/vm_sync_button.html +7 -0
- pve_sync_plugin/templates/pve_sync/pbsserverconfig.html +69 -0
- pve_sync_plugin/templates/pve_sync/pvebackupstatus.html +27 -0
- pve_sync_plugin/templates/pve_sync/pveclusterconfig.html +52 -0
- pve_sync_plugin/templates/pve_sync/pvedriftevent.html +89 -0
- pve_sync_plugin/templates/pve_sync/pvesyncjob.html +197 -0
- pve_sync_plugin/templates/pve_sync/pvewebhookevent.html +42 -0
- pve_sync_plugin/templates/pve_sync/settings.html +79 -0
- pve_sync_plugin/templatetags/__init__.py +1 -0
- pve_sync_plugin/templatetags/pve_sync_tags.py +86 -0
- pve_sync_plugin/urls.py +186 -0
- pve_sync_plugin/utils.py +84 -0
- pve_sync_plugin/view_helpers.py +98 -0
- pve_sync_plugin/views.py +535 -0
- state_db.py +543 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""NetBox plugin entrypoint for Proxmox Sync."""
|
|
2
|
+
|
|
3
|
+
from netbox.plugins import PluginConfig
|
|
4
|
+
|
|
5
|
+
__version__ = "2.1.0"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PveSyncPluginConfig(PluginConfig):
|
|
9
|
+
|
|
10
|
+
name = "pve_sync_plugin"
|
|
11
|
+
verbose_name = "Proxmox Sync"
|
|
12
|
+
description = "Synchronize Proxmox VE and Proxmox Backup Server inventory into NetBox."
|
|
13
|
+
author = "Proxmox Sync Maintainers"
|
|
14
|
+
author_email = ""
|
|
15
|
+
version = __version__
|
|
16
|
+
base_url = "pve-sync"
|
|
17
|
+
min_version = "4.5.0"
|
|
18
|
+
max_version = "4.6.99"
|
|
19
|
+
|
|
20
|
+
middleware = []
|
|
21
|
+
queues = ["default"]
|
|
22
|
+
|
|
23
|
+
required_settings = []
|
|
24
|
+
default_settings = {
|
|
25
|
+
"netbox_url": "",
|
|
26
|
+
"netbox_token": "",
|
|
27
|
+
"telegram_bot_token": "",
|
|
28
|
+
"telegram_chat_id": "",
|
|
29
|
+
"webhook_secret": "",
|
|
30
|
+
"default_cluster_name": "default",
|
|
31
|
+
"default_netbox_cluster": "Proxmox Cluster",
|
|
32
|
+
"default_site": "Main Datacenter",
|
|
33
|
+
"default_cluster_type": "Proxmox",
|
|
34
|
+
"default_node_role": "PVE",
|
|
35
|
+
"default_node_type": "Standard Server",
|
|
36
|
+
"state_db_path": "/var/lib/netbox/pve-sync-state.db",
|
|
37
|
+
"enable_backup_sync": True,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
def ready(self):
|
|
41
|
+
super().ready()
|
|
42
|
+
from pve_sync_plugin.signals import register_signals
|
|
43
|
+
register_signals()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
config = PveSyncPluginConfig
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""PVE Sync Plugin API package."""
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""API serializers for PVE Sync plugin models."""
|
|
2
|
+
|
|
3
|
+
from rest_framework import serializers
|
|
4
|
+
|
|
5
|
+
from netbox.api.serializers import NetBoxModelSerializer
|
|
6
|
+
|
|
7
|
+
from pve_sync_plugin.models import (
|
|
8
|
+
PveBackupStatus,
|
|
9
|
+
PveClusterConfig,
|
|
10
|
+
PvePluginSettings,
|
|
11
|
+
PveSyncJob,
|
|
12
|
+
PveWebhookEvent,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PveSyncJobSerializer(NetBoxModelSerializer):
|
|
17
|
+
url = serializers.HyperlinkedIdentityField(
|
|
18
|
+
view_name="plugins-api:pve_sync_plugin-api:pvesyncjob-detail",
|
|
19
|
+
)
|
|
20
|
+
duration = serializers.FloatField(read_only=True)
|
|
21
|
+
success_rate = serializers.FloatField(read_only=True)
|
|
22
|
+
|
|
23
|
+
class Meta:
|
|
24
|
+
model = PveSyncJob
|
|
25
|
+
fields = [
|
|
26
|
+
"id",
|
|
27
|
+
"url",
|
|
28
|
+
"display",
|
|
29
|
+
"cluster_name",
|
|
30
|
+
"status",
|
|
31
|
+
"trigger",
|
|
32
|
+
"start_time",
|
|
33
|
+
"end_time",
|
|
34
|
+
"duration",
|
|
35
|
+
"total_vms",
|
|
36
|
+
"success_vms",
|
|
37
|
+
"failed_vms",
|
|
38
|
+
"success_rate",
|
|
39
|
+
"nodes_offline",
|
|
40
|
+
"config_drifts",
|
|
41
|
+
"tag_changes",
|
|
42
|
+
"resource_alerts",
|
|
43
|
+
"details",
|
|
44
|
+
"triggered_by",
|
|
45
|
+
"created",
|
|
46
|
+
"last_updated",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class PveWebhookEventSerializer(NetBoxModelSerializer):
|
|
51
|
+
url = serializers.HyperlinkedIdentityField(
|
|
52
|
+
view_name="plugins-api:pve_sync_plugin-api:pvewebhookevent-detail",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
class Meta:
|
|
56
|
+
model = PveWebhookEvent
|
|
57
|
+
fields = [
|
|
58
|
+
"id",
|
|
59
|
+
"url",
|
|
60
|
+
"display",
|
|
61
|
+
"event_type",
|
|
62
|
+
"node",
|
|
63
|
+
"vmid",
|
|
64
|
+
"vm_name",
|
|
65
|
+
"raw_data",
|
|
66
|
+
"processed",
|
|
67
|
+
"processed_at",
|
|
68
|
+
"processing_error",
|
|
69
|
+
"sync_job",
|
|
70
|
+
"received_at",
|
|
71
|
+
"created",
|
|
72
|
+
"last_updated",
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PveClusterConfigSerializer(NetBoxModelSerializer):
|
|
77
|
+
url = serializers.HyperlinkedIdentityField(
|
|
78
|
+
view_name="plugins-api:pve_sync_plugin-api:pveclusterconfig-detail",
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
class Meta:
|
|
82
|
+
model = PveClusterConfig
|
|
83
|
+
fields = [
|
|
84
|
+
"id",
|
|
85
|
+
"url",
|
|
86
|
+
"display",
|
|
87
|
+
"name",
|
|
88
|
+
"description",
|
|
89
|
+
"pve_host",
|
|
90
|
+
"pve_user",
|
|
91
|
+
"pve_token",
|
|
92
|
+
"pve_secret",
|
|
93
|
+
"pve_verify_ssl",
|
|
94
|
+
"netbox_site",
|
|
95
|
+
"netbox_cluster_type",
|
|
96
|
+
"netbox_cluster",
|
|
97
|
+
"enabled",
|
|
98
|
+
"sync_schedule",
|
|
99
|
+
"last_sync",
|
|
100
|
+
"last_sync_status",
|
|
101
|
+
"created",
|
|
102
|
+
"last_updated",
|
|
103
|
+
]
|
|
104
|
+
# Never expose credentials in API responses
|
|
105
|
+
extra_kwargs = {
|
|
106
|
+
"pve_token": {"write_only": True},
|
|
107
|
+
"pve_secret": {"write_only": True},
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class PveBackupStatusSerializer(NetBoxModelSerializer):
|
|
112
|
+
url = serializers.HyperlinkedIdentityField(
|
|
113
|
+
view_name="plugins-api:pve_sync_plugin-api:pvebackupstatus-detail",
|
|
114
|
+
)
|
|
115
|
+
backup_age_days = serializers.IntegerField(read_only=True)
|
|
116
|
+
is_stale = serializers.BooleanField(read_only=True)
|
|
117
|
+
|
|
118
|
+
class Meta:
|
|
119
|
+
model = PveBackupStatus
|
|
120
|
+
fields = [
|
|
121
|
+
"id",
|
|
122
|
+
"url",
|
|
123
|
+
"display",
|
|
124
|
+
"vm",
|
|
125
|
+
"last_backup",
|
|
126
|
+
"backup_size",
|
|
127
|
+
"backup_status",
|
|
128
|
+
"backup_path",
|
|
129
|
+
"next_backup",
|
|
130
|
+
"pve_backup_id",
|
|
131
|
+
"backup_age_days",
|
|
132
|
+
"is_stale",
|
|
133
|
+
"created",
|
|
134
|
+
"last_updated",
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class PvePluginSettingsSerializer(NetBoxModelSerializer):
|
|
139
|
+
url = serializers.HyperlinkedIdentityField(
|
|
140
|
+
view_name="plugins-api:pve_sync_plugin-api:pvepluginsettings-detail",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
class Meta:
|
|
144
|
+
model = PvePluginSettings
|
|
145
|
+
fields = [
|
|
146
|
+
"id",
|
|
147
|
+
"url",
|
|
148
|
+
"pve_api_host",
|
|
149
|
+
"pve_api_user",
|
|
150
|
+
"pve_api_verify_ssl",
|
|
151
|
+
"netbox_url",
|
|
152
|
+
"default_cluster_name",
|
|
153
|
+
"default_netbox_cluster",
|
|
154
|
+
"default_site",
|
|
155
|
+
"default_cluster_type",
|
|
156
|
+
"default_node_role",
|
|
157
|
+
"default_node_type",
|
|
158
|
+
"state_db_path",
|
|
159
|
+
"enable_backup_sync",
|
|
160
|
+
]
|
|
161
|
+
# Never expose secrets via API
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""NetBox REST API routes for the PVE Sync plugin.
|
|
2
|
+
|
|
3
|
+
Mounted by NetBox under /api/plugins/pve-sync/.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from django.urls import path
|
|
7
|
+
from netbox.api.routers import NetBoxRouter
|
|
8
|
+
|
|
9
|
+
from pve_sync_plugin import views as plugin_views
|
|
10
|
+
|
|
11
|
+
from . import views as api_views
|
|
12
|
+
|
|
13
|
+
app_name = "pve_sync_plugin-api"
|
|
14
|
+
|
|
15
|
+
router = NetBoxRouter()
|
|
16
|
+
router.APIRootView.cls_name = "PveSyncPluginRootView"
|
|
17
|
+
|
|
18
|
+
router.register("jobs", api_views.PveSyncJobViewSet)
|
|
19
|
+
router.register("events", api_views.PveWebhookEventViewSet)
|
|
20
|
+
router.register("clusters", api_views.PveClusterConfigViewSet)
|
|
21
|
+
router.register("backup-status", api_views.PveBackupStatusViewSet)
|
|
22
|
+
router.register("settings", api_views.PvePluginSettingsViewSet)
|
|
23
|
+
|
|
24
|
+
urlpatterns = [
|
|
25
|
+
path("webhook/", plugin_views.webhook_receiver, name="webhook"),
|
|
26
|
+
*router.urls,
|
|
27
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""API ViewSets for PVE Sync plugin models."""
|
|
2
|
+
|
|
3
|
+
from rest_framework.decorators import action
|
|
4
|
+
from rest_framework.response import Response
|
|
5
|
+
from rest_framework import status as drf_status
|
|
6
|
+
|
|
7
|
+
from netbox.api.viewsets import NetBoxModelViewSet
|
|
8
|
+
|
|
9
|
+
from pve_sync_plugin.filtersets import (
|
|
10
|
+
PveBackupStatusFilterSet,
|
|
11
|
+
PveClusterConfigFilterSet,
|
|
12
|
+
PveSyncJobFilterSet,
|
|
13
|
+
PveWebhookEventFilterSet,
|
|
14
|
+
)
|
|
15
|
+
from pve_sync_plugin.models import (
|
|
16
|
+
PveBackupStatus,
|
|
17
|
+
PveClusterConfig,
|
|
18
|
+
PvePluginSettings,
|
|
19
|
+
PveSyncJob,
|
|
20
|
+
PveWebhookEvent,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .serializers import (
|
|
24
|
+
PveBackupStatusSerializer,
|
|
25
|
+
PveClusterConfigSerializer,
|
|
26
|
+
PvePluginSettingsSerializer,
|
|
27
|
+
PveSyncJobSerializer,
|
|
28
|
+
PveWebhookEventSerializer,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class PveSyncJobViewSet(NetBoxModelViewSet):
|
|
33
|
+
queryset = PveSyncJob.objects.all()
|
|
34
|
+
serializer_class = PveSyncJobSerializer
|
|
35
|
+
filterset_class = PveSyncJobFilterSet
|
|
36
|
+
|
|
37
|
+
@action(detail=False, methods=["post"], url_path="trigger")
|
|
38
|
+
def trigger_sync(self, request):
|
|
39
|
+
"""POST /api/plugins/pve-sync/jobs/trigger/ — start a new sync."""
|
|
40
|
+
from pve_sync_plugin.tasks import enqueue_sync
|
|
41
|
+
|
|
42
|
+
cluster_name = request.data.get("cluster", "default")
|
|
43
|
+
|
|
44
|
+
if cluster_name != "default":
|
|
45
|
+
if not PveClusterConfig.objects.filter(
|
|
46
|
+
name=cluster_name, enabled=True
|
|
47
|
+
).exists():
|
|
48
|
+
return Response(
|
|
49
|
+
{"detail": f"Cluster '{cluster_name}' not found or disabled."},
|
|
50
|
+
status=drf_status.HTTP_404_NOT_FOUND,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
job = PveSyncJob.objects.create(
|
|
54
|
+
cluster_name=cluster_name,
|
|
55
|
+
status="pending",
|
|
56
|
+
trigger="api",
|
|
57
|
+
triggered_by=request.user if request.user.is_authenticated else None,
|
|
58
|
+
details={},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
enqueue_sync(job)
|
|
63
|
+
except Exception as exc:
|
|
64
|
+
return Response(
|
|
65
|
+
{"detail": f"Failed to enqueue: {exc}"},
|
|
66
|
+
status=drf_status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
job.refresh_from_db()
|
|
70
|
+
serializer = self.get_serializer(job)
|
|
71
|
+
return Response(serializer.data, status=drf_status.HTTP_202_ACCEPTED)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class PveWebhookEventViewSet(NetBoxModelViewSet):
|
|
75
|
+
queryset = PveWebhookEvent.objects.all()
|
|
76
|
+
serializer_class = PveWebhookEventSerializer
|
|
77
|
+
filterset_class = PveWebhookEventFilterSet
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class PveClusterConfigViewSet(NetBoxModelViewSet):
|
|
81
|
+
queryset = PveClusterConfig.objects.all()
|
|
82
|
+
serializer_class = PveClusterConfigSerializer
|
|
83
|
+
filterset_class = PveClusterConfigFilterSet
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class PveBackupStatusViewSet(NetBoxModelViewSet):
|
|
87
|
+
queryset = PveBackupStatus.objects.select_related("vm")
|
|
88
|
+
serializer_class = PveBackupStatusSerializer
|
|
89
|
+
filterset_class = PveBackupStatusFilterSet
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class PvePluginSettingsViewSet(NetBoxModelViewSet):
|
|
93
|
+
queryset = PvePluginSettings.objects.all()
|
|
94
|
+
serializer_class = PvePluginSettingsSerializer
|
pve_sync_plugin/apps.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Enumerated choices for PVE Sync plugin models."""
|
|
2
|
+
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SyncJobStatusChoices(models.TextChoices):
|
|
7
|
+
STATUS_PENDING = "pending", "Pending"
|
|
8
|
+
STATUS_RUNNING = "running", "Running"
|
|
9
|
+
STATUS_SUCCESS = "success", "Success"
|
|
10
|
+
STATUS_FAILED = "failed", "Failed"
|
|
11
|
+
STATUS_PARTIAL = "partial", "Partial Success"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SyncJobTriggerChoices(models.TextChoices):
|
|
15
|
+
TRIGGER_MANUAL = "manual", "Manual"
|
|
16
|
+
TRIGGER_SCHEDULED = "scheduled", "Scheduled"
|
|
17
|
+
TRIGGER_WEBHOOK = "webhook", "Webhook"
|
|
18
|
+
TRIGGER_API = "api", "API Call"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WebhookEventChoices(models.TextChoices):
|
|
22
|
+
VM_STARTED = "vm-started", "VM Started"
|
|
23
|
+
VM_STOPPED = "vm-stopped", "VM Stopped"
|
|
24
|
+
VM_MIGRATED = "vm-migrated", "VM Migrated"
|
|
25
|
+
NODE_ONLINE = "node-online", "Node Online"
|
|
26
|
+
NODE_OFFLINE = "node-offline", "Node Offline"
|
|
27
|
+
BACKUP_DONE = "backup-done", "Backup Completed"
|
|
28
|
+
BACKUP_FAILED = "backup-failed", "Backup Failed"
|
|
29
|
+
CONFIG_CHANGE = "configuration-change", "Configuration Change"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BackupStatusChoices(models.TextChoices):
|
|
33
|
+
STATUS_SUCCESS = "success", "Success"
|
|
34
|
+
STATUS_FAILED = "failed", "Failed"
|
|
35
|
+
STATUS_RUNNING = "running", "Running"
|
|
36
|
+
STATUS_UNKNOWN = "unknown", "Unknown"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SyncScheduleChoices(models.TextChoices):
|
|
40
|
+
DISABLED = "disabled", "Disabled"
|
|
41
|
+
HOURLY = "hourly", "Hourly"
|
|
42
|
+
EVERY_3H = "every_3h", "Every 3 hours"
|
|
43
|
+
EVERY_6H = "every_6h", "Every 6 hours"
|
|
44
|
+
DAILY = "daily", "Daily"
|
|
45
|
+
WEEKLY = "weekly", "Weekly"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DriftTypeChoices(models.TextChoices):
|
|
49
|
+
HARDWARE = "hardware", "硬體配置變更"
|
|
50
|
+
MIGRATION = "migration", "VM 遷移"
|
|
51
|
+
IP_CHANGE = "ip_change", "IP 位址變更"
|
|
52
|
+
TAG_CHANGE = "tag_change", "標籤變更"
|
|
53
|
+
VM_CREATED = "vm_created", "VM 新增"
|
|
54
|
+
VM_DELETED = "vm_deleted", "VM 刪除"
|
|
55
|
+
VM_RENAMED = "vm_renamed", "VM 更名"
|
|
56
|
+
DISK_CHANGE = "disk_change", "磁碟配置變更"
|
|
57
|
+
DESCRIPTION_CHANGE = "description_change", "描述變更"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""FilterSets for PVE Sync plugin models (used by list views and API)."""
|
|
2
|
+
|
|
3
|
+
import django_filters
|
|
4
|
+
|
|
5
|
+
from netbox.filtersets import NetBoxModelFilterSet
|
|
6
|
+
|
|
7
|
+
from .choices import (
|
|
8
|
+
BackupStatusChoices,
|
|
9
|
+
DriftTypeChoices,
|
|
10
|
+
SyncJobStatusChoices,
|
|
11
|
+
SyncJobTriggerChoices,
|
|
12
|
+
SyncScheduleChoices,
|
|
13
|
+
WebhookEventChoices,
|
|
14
|
+
)
|
|
15
|
+
from .models import (
|
|
16
|
+
PbsServerConfig,
|
|
17
|
+
PveBackupStatus,
|
|
18
|
+
PveClusterConfig,
|
|
19
|
+
PveDriftEvent,
|
|
20
|
+
PveSyncJob,
|
|
21
|
+
PveWebhookEvent,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class PveSyncJobFilterSet(NetBoxModelFilterSet):
|
|
26
|
+
"""Filter sync jobs by status, trigger, cluster, and time range."""
|
|
27
|
+
|
|
28
|
+
status = django_filters.MultipleChoiceFilter(choices=SyncJobStatusChoices.choices)
|
|
29
|
+
trigger = django_filters.MultipleChoiceFilter(choices=SyncJobTriggerChoices.choices)
|
|
30
|
+
cluster_name = django_filters.CharFilter(lookup_expr="icontains")
|
|
31
|
+
start_time_after = django_filters.DateTimeFilter(
|
|
32
|
+
field_name="start_time", lookup_expr="gte"
|
|
33
|
+
)
|
|
34
|
+
start_time_before = django_filters.DateTimeFilter(
|
|
35
|
+
field_name="start_time", lookup_expr="lte"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
class Meta:
|
|
39
|
+
model = PveSyncJob
|
|
40
|
+
fields = ["id", "status", "trigger", "cluster_name"]
|
|
41
|
+
|
|
42
|
+
def search(self, queryset, name, value):
|
|
43
|
+
if not value.strip():
|
|
44
|
+
return queryset
|
|
45
|
+
return queryset.filter(cluster_name__icontains=value)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PveWebhookEventFilterSet(NetBoxModelFilterSet):
|
|
49
|
+
"""Filter webhook events by type, processing state, and VM."""
|
|
50
|
+
|
|
51
|
+
event_type = django_filters.MultipleChoiceFilter(
|
|
52
|
+
choices=WebhookEventChoices.choices
|
|
53
|
+
)
|
|
54
|
+
processed = django_filters.BooleanFilter()
|
|
55
|
+
vmid = django_filters.NumberFilter()
|
|
56
|
+
node = django_filters.CharFilter(lookup_expr="icontains")
|
|
57
|
+
|
|
58
|
+
class Meta:
|
|
59
|
+
model = PveWebhookEvent
|
|
60
|
+
fields = ["id", "event_type", "processed", "vmid", "node"]
|
|
61
|
+
|
|
62
|
+
def search(self, queryset, name, value):
|
|
63
|
+
if not value.strip():
|
|
64
|
+
return queryset
|
|
65
|
+
return queryset.filter(vm_name__icontains=value)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class PveClusterConfigFilterSet(NetBoxModelFilterSet):
|
|
69
|
+
"""Filter cluster configs by name, enabled state, and schedule."""
|
|
70
|
+
|
|
71
|
+
enabled = django_filters.BooleanFilter()
|
|
72
|
+
sync_schedule = django_filters.MultipleChoiceFilter(
|
|
73
|
+
choices=SyncScheduleChoices.choices
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
class Meta:
|
|
77
|
+
model = PveClusterConfig
|
|
78
|
+
fields = ["id", "name", "enabled", "sync_schedule"]
|
|
79
|
+
|
|
80
|
+
def search(self, queryset, name, value):
|
|
81
|
+
if not value.strip():
|
|
82
|
+
return queryset
|
|
83
|
+
return queryset.filter(name__icontains=value) | queryset.filter(
|
|
84
|
+
description__icontains=value
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class PbsServerConfigFilterSet(NetBoxModelFilterSet):
|
|
89
|
+
"""Filter PBS server configs by name and enabled state."""
|
|
90
|
+
|
|
91
|
+
enabled = django_filters.BooleanFilter()
|
|
92
|
+
|
|
93
|
+
class Meta:
|
|
94
|
+
model = PbsServerConfig
|
|
95
|
+
fields = ["id", "name", "enabled"]
|
|
96
|
+
|
|
97
|
+
def search(self, queryset, name, value):
|
|
98
|
+
if not value.strip():
|
|
99
|
+
return queryset
|
|
100
|
+
return queryset.filter(name__icontains=value) | queryset.filter(
|
|
101
|
+
description__icontains=value
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class PveDriftEventFilterSet(NetBoxModelFilterSet):
|
|
106
|
+
"""Filter drift events by type, VM name, cluster, and notification state."""
|
|
107
|
+
|
|
108
|
+
drift_type = django_filters.MultipleChoiceFilter(choices=DriftTypeChoices.choices)
|
|
109
|
+
vm_name = django_filters.CharFilter(lookup_expr="icontains")
|
|
110
|
+
cluster_name = django_filters.CharFilter(lookup_expr="icontains")
|
|
111
|
+
notified_telegram = django_filters.BooleanFilter()
|
|
112
|
+
|
|
113
|
+
class Meta:
|
|
114
|
+
model = PveDriftEvent
|
|
115
|
+
fields = ["id", "drift_type", "vmid", "cluster_name", "notified_telegram"]
|
|
116
|
+
|
|
117
|
+
def search(self, queryset, name, value):
|
|
118
|
+
if not value.strip():
|
|
119
|
+
return queryset
|
|
120
|
+
return queryset.filter(vm_name__icontains=value) | queryset.filter(cluster_name__icontains=value)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class PveBackupStatusFilterSet(NetBoxModelFilterSet):
|
|
124
|
+
"""Filter backup statuses by state."""
|
|
125
|
+
|
|
126
|
+
backup_status = django_filters.MultipleChoiceFilter(
|
|
127
|
+
choices=BackupStatusChoices.choices
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
class Meta:
|
|
131
|
+
model = PveBackupStatus
|
|
132
|
+
fields = ["id", "backup_status"]
|
|
133
|
+
|
|
134
|
+
def search(self, queryset, name, value):
|
|
135
|
+
if not value.strip():
|
|
136
|
+
return queryset
|
|
137
|
+
return queryset.filter(vm__name__icontains=value)
|