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,221 @@
|
|
|
1
|
+
"""Config bridge: translates plugin DB settings into the format expected by
|
|
2
|
+
the standalone sync engine (OptimizedPVEToNetBoxSync).
|
|
3
|
+
|
|
4
|
+
This module allows the sync engine to be driven either from:
|
|
5
|
+
1. Plugin DB models (PvePluginSettings / PveClusterConfig)
|
|
6
|
+
2. A YAML config file + env vars (standalone mode)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import tempfile
|
|
12
|
+
|
|
13
|
+
import yaml
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
# Environment variable keys managed by the config bridge.
|
|
18
|
+
# Used for setup and cleanup to avoid cross-worker pollution.
|
|
19
|
+
_ENV_KEYS = [
|
|
20
|
+
"PVE_SYNC_CONFIG_FILE",
|
|
21
|
+
"PVE_API_HOST",
|
|
22
|
+
"PVE_API_USER",
|
|
23
|
+
"PVE_API_TOKEN",
|
|
24
|
+
"PVE_API_SECRET",
|
|
25
|
+
"PVE_API_VERIFY_SSL",
|
|
26
|
+
"NB_API_URL",
|
|
27
|
+
"NB_API_TOKEN",
|
|
28
|
+
"TELEGRAM_BOT_TOKEN",
|
|
29
|
+
"TELEGRAM_CHAT_ID",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ConfigValidationError(Exception):
|
|
34
|
+
"""Raised when required configuration values are missing."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_config(config_data):
|
|
38
|
+
"""Validate that required fields are present and non-empty.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
ConfigValidationError: if critical connection details are missing.
|
|
42
|
+
"""
|
|
43
|
+
cluster = config_data.get("clusters", [{}])[0]
|
|
44
|
+
pve = cluster.get("pve", {})
|
|
45
|
+
netbox = cluster.get("netbox", {})
|
|
46
|
+
|
|
47
|
+
missing = []
|
|
48
|
+
if not pve.get("host"):
|
|
49
|
+
missing.append("pve.host (PVE API host)")
|
|
50
|
+
if not pve.get("token"):
|
|
51
|
+
missing.append("pve.token (PVE API token)")
|
|
52
|
+
if not pve.get("secret"):
|
|
53
|
+
missing.append("pve.secret (PVE API secret)")
|
|
54
|
+
if not netbox.get("url"):
|
|
55
|
+
missing.append("netbox.url (NetBox API URL)")
|
|
56
|
+
if not netbox.get("token"):
|
|
57
|
+
missing.append("netbox.token (NetBox API token)")
|
|
58
|
+
|
|
59
|
+
if missing:
|
|
60
|
+
raise ConfigValidationError(
|
|
61
|
+
f"Missing required config fields: {', '.join(missing)}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def build_runtime_config_from_db(cluster_name="default"):
|
|
66
|
+
"""Read plugin DB models and produce a config dict compatible with
|
|
67
|
+
the standalone sync engine.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
dict: config in the same schema as config.yaml
|
|
71
|
+
"""
|
|
72
|
+
from pve_sync_plugin.models import PveClusterConfig
|
|
73
|
+
from pve_sync_plugin.utils import get_plugin_config
|
|
74
|
+
|
|
75
|
+
cluster = PveClusterConfig.objects.filter(
|
|
76
|
+
name=cluster_name, enabled=True
|
|
77
|
+
).first()
|
|
78
|
+
|
|
79
|
+
if cluster:
|
|
80
|
+
pve_host = cluster.pve_host
|
|
81
|
+
pve_user = cluster.pve_user
|
|
82
|
+
pve_token = cluster.pve_token
|
|
83
|
+
pve_secret = cluster.pve_secret
|
|
84
|
+
verify_ssl = cluster.pve_verify_ssl
|
|
85
|
+
site_name = (
|
|
86
|
+
cluster.netbox_site.name
|
|
87
|
+
if cluster.netbox_site
|
|
88
|
+
else get_plugin_config("default_site", "Main Datacenter")
|
|
89
|
+
)
|
|
90
|
+
cluster_type = (
|
|
91
|
+
cluster.netbox_cluster_type.name
|
|
92
|
+
if cluster.netbox_cluster_type
|
|
93
|
+
else get_plugin_config("default_cluster_type", "Proxmox")
|
|
94
|
+
)
|
|
95
|
+
netbox_cluster = (
|
|
96
|
+
cluster.netbox_cluster.name
|
|
97
|
+
if cluster.netbox_cluster
|
|
98
|
+
else get_plugin_config("default_netbox_cluster", "Proxmox Cluster")
|
|
99
|
+
)
|
|
100
|
+
logical_name = cluster.name
|
|
101
|
+
notify_on_sync = cluster.notify_on_sync
|
|
102
|
+
else:
|
|
103
|
+
raise ConfigValidationError(
|
|
104
|
+
f"No enabled PVE cluster config found for cluster '{cluster_name}'. "
|
|
105
|
+
"Please add a cluster in PVE Clusters → Add Cluster."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
telegram_enabled = notify_on_sync and bool(
|
|
109
|
+
get_plugin_config("telegram_bot_token", "")
|
|
110
|
+
and get_plugin_config("telegram_chat_id", "")
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
"clusters": [
|
|
115
|
+
{
|
|
116
|
+
"name": logical_name,
|
|
117
|
+
"pve": {
|
|
118
|
+
"host": pve_host,
|
|
119
|
+
"user": pve_user,
|
|
120
|
+
"token": pve_token,
|
|
121
|
+
"secret": pve_secret,
|
|
122
|
+
"verify_ssl": bool(verify_ssl),
|
|
123
|
+
},
|
|
124
|
+
"netbox": {
|
|
125
|
+
"url": get_plugin_config("netbox_url", ""),
|
|
126
|
+
"token": get_plugin_config("netbox_token", ""),
|
|
127
|
+
},
|
|
128
|
+
"settings": {
|
|
129
|
+
"cluster_name": netbox_cluster,
|
|
130
|
+
"site_name": site_name,
|
|
131
|
+
"cluster_type": cluster_type,
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"telegram": {
|
|
136
|
+
"enabled": telegram_enabled,
|
|
137
|
+
"bot_token": get_plugin_config("telegram_bot_token", ""),
|
|
138
|
+
"chat_id": get_plugin_config("telegram_chat_id", ""),
|
|
139
|
+
},
|
|
140
|
+
"monitoring": {
|
|
141
|
+
"node_offline_alert": True,
|
|
142
|
+
"config_drift_alert": True,
|
|
143
|
+
"tag_change_alert": True,
|
|
144
|
+
"resource_alert": {
|
|
145
|
+
"enabled": True,
|
|
146
|
+
"memory_threshold": 85,
|
|
147
|
+
"cpu_threshold": 90,
|
|
148
|
+
"disk_threshold": 10,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
"sync": {
|
|
152
|
+
"incremental": True,
|
|
153
|
+
"force_full_sync": False,
|
|
154
|
+
"batch_size": 50,
|
|
155
|
+
"default_node_role": get_plugin_config(
|
|
156
|
+
"default_node_role", "PVE"
|
|
157
|
+
),
|
|
158
|
+
"default_node_type": get_plugin_config(
|
|
159
|
+
"default_node_type", "Standard Server"
|
|
160
|
+
),
|
|
161
|
+
},
|
|
162
|
+
"state_db": {
|
|
163
|
+
"path": get_plugin_config(
|
|
164
|
+
"state_db_path", "/var/lib/pve-sync/state.db"
|
|
165
|
+
),
|
|
166
|
+
"cleanup_days": 90,
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def write_runtime_config_file(cluster_name="default"):
|
|
172
|
+
"""Write a temporary YAML config file and set env vars.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
str: path to the temporary config file (caller must clean up)
|
|
176
|
+
|
|
177
|
+
Raises:
|
|
178
|
+
ConfigValidationError: if required config values are missing.
|
|
179
|
+
"""
|
|
180
|
+
config_data = build_runtime_config_from_db(cluster_name)
|
|
181
|
+
validate_config(config_data)
|
|
182
|
+
|
|
183
|
+
config_file = tempfile.NamedTemporaryFile(
|
|
184
|
+
"w", prefix="pve-sync-", suffix=".yaml", delete=False
|
|
185
|
+
)
|
|
186
|
+
with config_file:
|
|
187
|
+
yaml.safe_dump(config_data, config_file, allow_unicode=True, sort_keys=False)
|
|
188
|
+
|
|
189
|
+
_apply_runtime_env(config_file.name, config_data)
|
|
190
|
+
return config_file.name
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _apply_runtime_env(config_path, config_data):
|
|
194
|
+
"""Set environment variables expected by the standalone sync engine."""
|
|
195
|
+
os.environ["PVE_SYNC_CONFIG_FILE"] = config_path
|
|
196
|
+
cluster = config_data["clusters"][0]
|
|
197
|
+
pve = cluster["pve"]
|
|
198
|
+
netbox = cluster["netbox"]
|
|
199
|
+
telegram = config_data.get("telegram", {})
|
|
200
|
+
|
|
201
|
+
os.environ["PVE_API_HOST"] = str(pve.get("host", ""))
|
|
202
|
+
os.environ["PVE_API_USER"] = str(pve.get("user", ""))
|
|
203
|
+
os.environ["PVE_API_TOKEN"] = str(pve.get("token", ""))
|
|
204
|
+
os.environ["PVE_API_SECRET"] = str(pve.get("secret", ""))
|
|
205
|
+
os.environ["PVE_API_VERIFY_SSL"] = (
|
|
206
|
+
"true" if pve.get("verify_ssl") else "false"
|
|
207
|
+
)
|
|
208
|
+
os.environ["NB_API_URL"] = str(netbox.get("url", ""))
|
|
209
|
+
os.environ["NB_API_TOKEN"] = str(netbox.get("token", ""))
|
|
210
|
+
os.environ["TELEGRAM_BOT_TOKEN"] = str(telegram.get("bot_token", ""))
|
|
211
|
+
os.environ["TELEGRAM_CHAT_ID"] = str(telegram.get("chat_id", ""))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def cleanup_runtime_env():
|
|
215
|
+
"""Remove temporary environment variables set by _apply_runtime_env.
|
|
216
|
+
|
|
217
|
+
Must be called in a finally block after sync completes to prevent
|
|
218
|
+
env var pollution across RQ worker invocations.
|
|
219
|
+
"""
|
|
220
|
+
for key in _ENV_KEYS:
|
|
221
|
+
os.environ.pop(key, None)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PVESyncEngine:
|
|
8
|
+
"""Plugin-facing wrapper around the standalone OptimizedPVEToNetBoxSync.
|
|
9
|
+
|
|
10
|
+
Usage in tasks.py::
|
|
11
|
+
|
|
12
|
+
from pve_sync_plugin.sync import PVESyncEngine
|
|
13
|
+
|
|
14
|
+
engine = PVESyncEngine(config_path="/tmp/pve-sync-xxx.yaml")
|
|
15
|
+
engine.run()
|
|
16
|
+
stats = engine.stats
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config_path=None, job_id=None):
|
|
20
|
+
self.config_path = config_path
|
|
21
|
+
self.job_id = job_id
|
|
22
|
+
self._sync_instance = None
|
|
23
|
+
self.stats = {}
|
|
24
|
+
|
|
25
|
+
def run(self):
|
|
26
|
+
"""Execute a full sync cycle using the standalone engine."""
|
|
27
|
+
try:
|
|
28
|
+
config_module = importlib.import_module("config")
|
|
29
|
+
sync_module = importlib.import_module("pve_sync")
|
|
30
|
+
except ImportError as exc:
|
|
31
|
+
logger.error(
|
|
32
|
+
"Cannot import standalone sync modules. "
|
|
33
|
+
"Make sure the project root is accessible: %s",
|
|
34
|
+
exc,
|
|
35
|
+
)
|
|
36
|
+
raise
|
|
37
|
+
|
|
38
|
+
# Re-initialize config with our temp file
|
|
39
|
+
if self.config_path:
|
|
40
|
+
config_module._global_config = None
|
|
41
|
+
config_module.init_config(self.config_path)
|
|
42
|
+
|
|
43
|
+
# Run the sync
|
|
44
|
+
self._sync_instance = sync_module.OptimizedPVEToNetBoxSync(job_id=self.job_id)
|
|
45
|
+
self._sync_instance.sync()
|
|
46
|
+
|
|
47
|
+
# Capture stats
|
|
48
|
+
self.stats = dict(getattr(self._sync_instance, "stats", {}))
|
|
49
|
+
|
|
50
|
+
return self.stats
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""NetBox tables for PVE Sync plugin list views."""
|
|
2
|
+
|
|
3
|
+
import django_tables2 as tables
|
|
4
|
+
|
|
5
|
+
from netbox.tables import NetBoxTable, columns
|
|
6
|
+
|
|
7
|
+
from .models import (
|
|
8
|
+
PbsServerConfig,
|
|
9
|
+
PveBackupStatus,
|
|
10
|
+
PveClusterConfig,
|
|
11
|
+
PveDriftEvent,
|
|
12
|
+
PveSyncJob,
|
|
13
|
+
PveWebhookEvent,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
# PveSyncJob
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
class PveSyncJobTable(NetBoxTable):
|
|
22
|
+
"""Table for sync job history with status badges and timing information."""
|
|
23
|
+
|
|
24
|
+
id = tables.Column(linkify=True)
|
|
25
|
+
cluster_name = tables.Column(linkify=True)
|
|
26
|
+
status = columns.ChoiceFieldColumn()
|
|
27
|
+
trigger = columns.ChoiceFieldColumn()
|
|
28
|
+
start_time = columns.DateTimeColumn()
|
|
29
|
+
end_time = columns.DateTimeColumn()
|
|
30
|
+
success_rate = tables.Column(
|
|
31
|
+
accessor="success_rate",
|
|
32
|
+
verbose_name="Success %",
|
|
33
|
+
orderable=False,
|
|
34
|
+
)
|
|
35
|
+
triggered_by = tables.Column(linkify=True)
|
|
36
|
+
actions = columns.ActionsColumn(actions=("delete", "changelog"))
|
|
37
|
+
|
|
38
|
+
class Meta(NetBoxTable.Meta):
|
|
39
|
+
model = PveSyncJob
|
|
40
|
+
fields = (
|
|
41
|
+
"pk",
|
|
42
|
+
"id",
|
|
43
|
+
"cluster_name",
|
|
44
|
+
"status",
|
|
45
|
+
"trigger",
|
|
46
|
+
"start_time",
|
|
47
|
+
"end_time",
|
|
48
|
+
"total_vms",
|
|
49
|
+
"success_vms",
|
|
50
|
+
"failed_vms",
|
|
51
|
+
"success_rate",
|
|
52
|
+
"nodes_offline",
|
|
53
|
+
"config_drifts",
|
|
54
|
+
"resource_alerts",
|
|
55
|
+
"triggered_by",
|
|
56
|
+
)
|
|
57
|
+
default_columns = (
|
|
58
|
+
"id",
|
|
59
|
+
"cluster_name",
|
|
60
|
+
"status",
|
|
61
|
+
"trigger",
|
|
62
|
+
"start_time",
|
|
63
|
+
"end_time",
|
|
64
|
+
"total_vms",
|
|
65
|
+
"success_rate",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# PveWebhookEvent
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
class PveWebhookEventTable(NetBoxTable):
|
|
74
|
+
"""Table for webhook events received from PVE."""
|
|
75
|
+
|
|
76
|
+
id = tables.Column(linkify=True)
|
|
77
|
+
event_type = columns.ChoiceFieldColumn()
|
|
78
|
+
node = tables.Column()
|
|
79
|
+
vmid = tables.Column(verbose_name="VM ID")
|
|
80
|
+
vm_name = tables.Column()
|
|
81
|
+
processed = columns.BooleanColumn()
|
|
82
|
+
received_at = columns.DateTimeColumn()
|
|
83
|
+
sync_job = tables.Column(linkify=True)
|
|
84
|
+
actions = columns.ActionsColumn(actions=("delete", "changelog"))
|
|
85
|
+
|
|
86
|
+
class Meta(NetBoxTable.Meta):
|
|
87
|
+
model = PveWebhookEvent
|
|
88
|
+
fields = (
|
|
89
|
+
"pk",
|
|
90
|
+
"id",
|
|
91
|
+
"event_type",
|
|
92
|
+
"node",
|
|
93
|
+
"vmid",
|
|
94
|
+
"vm_name",
|
|
95
|
+
"processed",
|
|
96
|
+
"received_at",
|
|
97
|
+
"sync_job",
|
|
98
|
+
)
|
|
99
|
+
default_columns = (
|
|
100
|
+
"id",
|
|
101
|
+
"event_type",
|
|
102
|
+
"node",
|
|
103
|
+
"vmid",
|
|
104
|
+
"vm_name",
|
|
105
|
+
"processed",
|
|
106
|
+
"received_at",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# PveClusterConfig
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
class PveClusterConfigTable(NetBoxTable):
|
|
115
|
+
"""Table for PVE cluster connection profiles."""
|
|
116
|
+
|
|
117
|
+
name = tables.Column(linkify=True)
|
|
118
|
+
pve_host = tables.Column()
|
|
119
|
+
enabled = columns.BooleanColumn()
|
|
120
|
+
sync_schedule = columns.ChoiceFieldColumn()
|
|
121
|
+
netbox_site = tables.Column(linkify=True)
|
|
122
|
+
netbox_cluster = tables.Column(linkify=True)
|
|
123
|
+
last_sync = columns.DateTimeColumn()
|
|
124
|
+
last_sync_status = tables.Column()
|
|
125
|
+
|
|
126
|
+
class Meta(NetBoxTable.Meta):
|
|
127
|
+
model = PveClusterConfig
|
|
128
|
+
fields = (
|
|
129
|
+
"pk",
|
|
130
|
+
"name",
|
|
131
|
+
"pve_host",
|
|
132
|
+
"pve_user",
|
|
133
|
+
"enabled",
|
|
134
|
+
"sync_schedule",
|
|
135
|
+
"netbox_site",
|
|
136
|
+
"netbox_cluster",
|
|
137
|
+
"last_sync",
|
|
138
|
+
"last_sync_status",
|
|
139
|
+
)
|
|
140
|
+
default_columns = (
|
|
141
|
+
"name",
|
|
142
|
+
"pve_host",
|
|
143
|
+
"enabled",
|
|
144
|
+
"sync_schedule",
|
|
145
|
+
"netbox_site",
|
|
146
|
+
"netbox_cluster",
|
|
147
|
+
"last_sync",
|
|
148
|
+
"last_sync_status",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
# PbsServerConfig
|
|
154
|
+
# ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
class PbsServerConfigTable(NetBoxTable):
|
|
157
|
+
"""Table for PBS server connection profiles."""
|
|
158
|
+
|
|
159
|
+
name = tables.Column(linkify=True)
|
|
160
|
+
pbs_host = tables.Column()
|
|
161
|
+
pbs_node_name = tables.Column()
|
|
162
|
+
enabled = columns.BooleanColumn()
|
|
163
|
+
netbox_site = tables.Column(linkify=True)
|
|
164
|
+
last_sync = columns.DateTimeColumn()
|
|
165
|
+
last_sync_status = tables.Column()
|
|
166
|
+
actions = columns.ActionsColumn(actions=("edit", "delete", "changelog"))
|
|
167
|
+
|
|
168
|
+
class Meta(NetBoxTable.Meta):
|
|
169
|
+
model = PbsServerConfig
|
|
170
|
+
fields = (
|
|
171
|
+
"pk",
|
|
172
|
+
"name",
|
|
173
|
+
"pbs_host",
|
|
174
|
+
"pbs_node_name",
|
|
175
|
+
"enabled",
|
|
176
|
+
"netbox_site",
|
|
177
|
+
"last_sync",
|
|
178
|
+
"last_sync_status",
|
|
179
|
+
)
|
|
180
|
+
default_columns = (
|
|
181
|
+
"name",
|
|
182
|
+
"pbs_host",
|
|
183
|
+
"pbs_node_name",
|
|
184
|
+
"enabled",
|
|
185
|
+
"netbox_site",
|
|
186
|
+
"last_sync",
|
|
187
|
+
"last_sync_status",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# ---------------------------------------------------------------------------
|
|
192
|
+
# PveDriftEvent
|
|
193
|
+
# ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
class PveDriftEventTable(NetBoxTable):
|
|
196
|
+
"""Table for VM configuration drift events."""
|
|
197
|
+
|
|
198
|
+
id = tables.Column(linkify=True)
|
|
199
|
+
vm_name = tables.Column(verbose_name="VM 名稱")
|
|
200
|
+
vmid = tables.Column(verbose_name="VM ID")
|
|
201
|
+
cluster_name = tables.Column(verbose_name="叢集")
|
|
202
|
+
drift_type = columns.ChoiceFieldColumn(verbose_name="變更類型")
|
|
203
|
+
field_name = tables.Column(verbose_name="欄位")
|
|
204
|
+
old_value = tables.Column(verbose_name="舊值")
|
|
205
|
+
new_value = tables.Column(verbose_name="新值")
|
|
206
|
+
notified_telegram = columns.BooleanColumn(verbose_name="已通知")
|
|
207
|
+
created = columns.DateTimeColumn(verbose_name="偵測時間")
|
|
208
|
+
sync_job = tables.Column(linkify=True, verbose_name="同步任務")
|
|
209
|
+
actions = columns.ActionsColumn(actions=("delete",))
|
|
210
|
+
|
|
211
|
+
class Meta(NetBoxTable.Meta):
|
|
212
|
+
model = PveDriftEvent
|
|
213
|
+
fields = (
|
|
214
|
+
"pk",
|
|
215
|
+
"id",
|
|
216
|
+
"vm_name",
|
|
217
|
+
"vmid",
|
|
218
|
+
"cluster_name",
|
|
219
|
+
"drift_type",
|
|
220
|
+
"field_name",
|
|
221
|
+
"old_value",
|
|
222
|
+
"new_value",
|
|
223
|
+
"notified_telegram",
|
|
224
|
+
"created",
|
|
225
|
+
"sync_job",
|
|
226
|
+
)
|
|
227
|
+
default_columns = (
|
|
228
|
+
"id",
|
|
229
|
+
"vm_name",
|
|
230
|
+
"vmid",
|
|
231
|
+
"cluster_name",
|
|
232
|
+
"drift_type",
|
|
233
|
+
"field_name",
|
|
234
|
+
"old_value",
|
|
235
|
+
"new_value",
|
|
236
|
+
"notified_telegram",
|
|
237
|
+
"created",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
# ---------------------------------------------------------------------------
|
|
242
|
+
# PveBackupStatus
|
|
243
|
+
# ---------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
class PveBackupStatusTable(NetBoxTable):
|
|
246
|
+
"""Table for per-VM backup tracking."""
|
|
247
|
+
|
|
248
|
+
id = tables.Column(linkify=True)
|
|
249
|
+
vm = tables.Column(linkify=True, verbose_name="Virtual Machine")
|
|
250
|
+
backup_status = columns.ChoiceFieldColumn()
|
|
251
|
+
last_backup = columns.DateTimeColumn()
|
|
252
|
+
backup_age_days = tables.Column(
|
|
253
|
+
accessor="backup_age_days",
|
|
254
|
+
verbose_name="Age (days)",
|
|
255
|
+
orderable=False,
|
|
256
|
+
)
|
|
257
|
+
next_backup = columns.DateTimeColumn()
|
|
258
|
+
actions = columns.ActionsColumn(actions=("changelog",))
|
|
259
|
+
|
|
260
|
+
class Meta(NetBoxTable.Meta):
|
|
261
|
+
model = PveBackupStatus
|
|
262
|
+
fields = (
|
|
263
|
+
"pk",
|
|
264
|
+
"id",
|
|
265
|
+
"vm",
|
|
266
|
+
"backup_status",
|
|
267
|
+
"last_backup",
|
|
268
|
+
"backup_age_days",
|
|
269
|
+
"backup_size",
|
|
270
|
+
"next_backup",
|
|
271
|
+
)
|
|
272
|
+
default_columns = (
|
|
273
|
+
"id",
|
|
274
|
+
"vm",
|
|
275
|
+
"backup_status",
|
|
276
|
+
"last_backup",
|
|
277
|
+
"backup_age_days",
|
|
278
|
+
)
|