proxsync 2.0.2__tar.gz → 2.0.3__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.
- {proxsync-2.0.2/proxsync.egg-info → proxsync-2.0.3}/PKG-INFO +1 -1
- {proxsync-2.0.2 → proxsync-2.0.3/proxsync.egg-info}/PKG-INFO +1 -1
- {proxsync-2.0.2 → proxsync-2.0.3}/proxsync.egg-info/SOURCES.txt +4 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync.py +123 -1
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/api/serializers.py +17 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/api/urls.py +2 -1
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/api/views.py +7 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/choices.py +13 -0
- proxsync-2.0.3/pve_sync_plugin/migrations/0015_pvevmtasklog.py +55 -0
- proxsync-2.0.3/pve_sync_plugin/migrations/0016_pvevmtasklog_status_textfield.py +18 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/models.py +57 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/navigation.py +6 -1
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/tables.py +25 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/template_content.py +35 -2
- proxsync-2.0.3/pve_sync_plugin/templates/pve_sync/inc/vm_task_log_panel.html +57 -0
- proxsync-2.0.3/pve_sync_plugin/templates/pve_sync/pvevmtasklog_list.html +10 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/urls.py +8 -1
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/views.py +21 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pyproject.toml +1 -1
- {proxsync-2.0.2 → proxsync-2.0.3}/MANIFEST.in +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/README.md +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/config.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pbs_sync.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/proxsync.egg-info/dependency_links.txt +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/proxsync.egg-info/requires.txt +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/proxsync.egg-info/top_level.txt +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/__init__.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/api/__init__.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/apps.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/filtersets.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/forms.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/management/__init__.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/management/commands/__init__.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/management/commands/pve_sync.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/management/commands/run_scheduled_syncs.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0001_initial.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0002_pvepluginsettings.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0003_netboxmodel_timestamps.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0004_model_verbose_names.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0005_align_netboxmodel_timestamps.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0006_pbsserverconfig_and_more.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0007_rename_verbose_names_to_proxmox_sync.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0008_pbsserverconfig_sync_schedule.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0009_add_every_3h_schedule.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0010_pvedriftevent.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0011_pveclusterconfig_notify_on_sync.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0012_pvedriftevent_tags_and_index_renames.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0013_pluginsettings_log_retention.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0014_vmprovisioninglog.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/__init__.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/signals.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/sync/__init__.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/sync/config_bridge.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/sync/engine.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/tasks.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/dashboard.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/inc/vm_sync_button.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/pbsserverconfig.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/pvebackupstatus.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/pveclusterconfig.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/pvedriftevent.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/pvesyncjob.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/pvewebhookevent.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/settings.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/vm_provisioning_combined.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/vm_provisioning_log.html +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templatetags/__init__.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templatetags/pve_sync_tags.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/utils.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/view_helpers.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/setup.cfg +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/state_db.py +0 -0
- {proxsync-2.0.2 → proxsync-2.0.3}/tests/test_plugin_contracts.py +0 -0
|
@@ -47,6 +47,8 @@ pve_sync_plugin/migrations/0011_pveclusterconfig_notify_on_sync.py
|
|
|
47
47
|
pve_sync_plugin/migrations/0012_pvedriftevent_tags_and_index_renames.py
|
|
48
48
|
pve_sync_plugin/migrations/0013_pluginsettings_log_retention.py
|
|
49
49
|
pve_sync_plugin/migrations/0014_vmprovisioninglog.py
|
|
50
|
+
pve_sync_plugin/migrations/0015_pvevmtasklog.py
|
|
51
|
+
pve_sync_plugin/migrations/0016_pvevmtasklog_status_textfield.py
|
|
50
52
|
pve_sync_plugin/migrations/__init__.py
|
|
51
53
|
pve_sync_plugin/sync/__init__.py
|
|
52
54
|
pve_sync_plugin/sync/config_bridge.py
|
|
@@ -57,11 +59,13 @@ pve_sync_plugin/templates/pve_sync/pvebackupstatus.html
|
|
|
57
59
|
pve_sync_plugin/templates/pve_sync/pveclusterconfig.html
|
|
58
60
|
pve_sync_plugin/templates/pve_sync/pvedriftevent.html
|
|
59
61
|
pve_sync_plugin/templates/pve_sync/pvesyncjob.html
|
|
62
|
+
pve_sync_plugin/templates/pve_sync/pvevmtasklog_list.html
|
|
60
63
|
pve_sync_plugin/templates/pve_sync/pvewebhookevent.html
|
|
61
64
|
pve_sync_plugin/templates/pve_sync/settings.html
|
|
62
65
|
pve_sync_plugin/templates/pve_sync/vm_provisioning_combined.html
|
|
63
66
|
pve_sync_plugin/templates/pve_sync/vm_provisioning_log.html
|
|
64
67
|
pve_sync_plugin/templates/pve_sync/inc/vm_sync_button.html
|
|
68
|
+
pve_sync_plugin/templates/pve_sync/inc/vm_task_log_panel.html
|
|
65
69
|
pve_sync_plugin/templatetags/__init__.py
|
|
66
70
|
pve_sync_plugin/templatetags/pve_sync_tags.py
|
|
67
71
|
tests/test_plugin_contracts.py
|
|
@@ -1235,6 +1235,120 @@ class OptimizedPVEToNetBoxSync:
|
|
|
1235
1235
|
print(f"⚠️ 清理孤兒 VM 失敗: {exc}")
|
|
1236
1236
|
traceback.print_exc()
|
|
1237
1237
|
|
|
1238
|
+
def sync_vm_tasks(self):
|
|
1239
|
+
"""從 PVE 各節點抓取 VM 操作記錄(建立/刪除/Clone/遷移)並同步到 NetBox。"""
|
|
1240
|
+
try:
|
|
1241
|
+
from django.utils import timezone
|
|
1242
|
+
from pve_sync_plugin.models import PveVmTaskLog
|
|
1243
|
+
except ImportError:
|
|
1244
|
+
return
|
|
1245
|
+
|
|
1246
|
+
VM_TASK_TYPES = {
|
|
1247
|
+
"qmcreate", "qmdestroy", "qmclone", "qmmigrate", "qmrestore",
|
|
1248
|
+
"qmstart", "qmstop", "qmshutdown", "qmreboot",
|
|
1249
|
+
}
|
|
1250
|
+
NOTIFY_TYPES = {"qmdestroy", "qmclone", "qmrestore", "qmshutdown", "qmstop"}
|
|
1251
|
+
TASK_LABELS = {
|
|
1252
|
+
"qmcreate": "🆕 VM 建立",
|
|
1253
|
+
"qmdestroy": "🗑️ VM 刪除",
|
|
1254
|
+
"qmclone": "📋 VM Clone",
|
|
1255
|
+
"qmmigrate": "🔀 VM 遷移",
|
|
1256
|
+
"qmrestore": "♻️ VM 還原",
|
|
1257
|
+
"qmstart": "▶️ VM 開機",
|
|
1258
|
+
"qmstop": "⏹️ VM 強制關機",
|
|
1259
|
+
"qmshutdown": "🔽 VM 正常關機",
|
|
1260
|
+
"qmreboot": "🔄 VM 重新開機",
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
# 從 NetBox cache 建立 vmid → vm_name 對照
|
|
1264
|
+
vmid_to_name: dict = {}
|
|
1265
|
+
for key, vm in self.nb_cache.get("virtual_machines_by_serial", {}).items():
|
|
1266
|
+
try:
|
|
1267
|
+
vmid_to_name[int(vm.serial)] = vm.name
|
|
1268
|
+
except (ValueError, TypeError, AttributeError):
|
|
1269
|
+
pass
|
|
1270
|
+
|
|
1271
|
+
# 只通知最近 2 小時內的操作(避免首次匯入時大量通知)
|
|
1272
|
+
notify_cutoff = time.time() - 7200
|
|
1273
|
+
|
|
1274
|
+
since_ts = int(time.time()) - 30 * 86400 # 抓最近 30 天
|
|
1275
|
+
new_count = 0
|
|
1276
|
+
|
|
1277
|
+
for node_name in self.pve_cache.get("vms_by_node", {}):
|
|
1278
|
+
try:
|
|
1279
|
+
tasks = self.pve_api.nodes(node_name).tasks.get(since=since_ts, limit=1000)
|
|
1280
|
+
except Exception as exc:
|
|
1281
|
+
print(f" ⚠️ 無法取得節點 {node_name} 的操作記錄: {exc}")
|
|
1282
|
+
continue
|
|
1283
|
+
|
|
1284
|
+
for task in tasks:
|
|
1285
|
+
if task.get("type") not in VM_TASK_TYPES:
|
|
1286
|
+
continue
|
|
1287
|
+
|
|
1288
|
+
upid = task.get("upid", "")
|
|
1289
|
+
if not upid:
|
|
1290
|
+
continue
|
|
1291
|
+
|
|
1292
|
+
vmid_str = task.get("id", "")
|
|
1293
|
+
try:
|
|
1294
|
+
vmid = int(vmid_str)
|
|
1295
|
+
except (ValueError, TypeError):
|
|
1296
|
+
continue
|
|
1297
|
+
|
|
1298
|
+
# UPID 去重
|
|
1299
|
+
if PveVmTaskLog.objects.filter(upid=upid).exists():
|
|
1300
|
+
continue
|
|
1301
|
+
|
|
1302
|
+
task_type = task.get("type")
|
|
1303
|
+
operator = task.get("user", "unknown")
|
|
1304
|
+
start_ts = task.get("starttime")
|
|
1305
|
+
end_ts = task.get("endtime")
|
|
1306
|
+
status = task.get("status", "") or ""
|
|
1307
|
+
vm_name = vmid_to_name.get(vmid, f"VM-{vmid}")
|
|
1308
|
+
|
|
1309
|
+
import datetime as _dt
|
|
1310
|
+
_utc = _dt.timezone.utc
|
|
1311
|
+
start_dt = _dt.datetime.fromtimestamp(start_ts, tz=_utc) if start_ts else timezone.now()
|
|
1312
|
+
end_dt = _dt.datetime.fromtimestamp(end_ts, tz=_utc) if end_ts else None
|
|
1313
|
+
|
|
1314
|
+
log = PveVmTaskLog.objects.create(
|
|
1315
|
+
upid=upid,
|
|
1316
|
+
vmid=vmid,
|
|
1317
|
+
vm_name=vm_name,
|
|
1318
|
+
cluster_name=self.cluster_name,
|
|
1319
|
+
node=node_name,
|
|
1320
|
+
task_type=task_type,
|
|
1321
|
+
operator=operator,
|
|
1322
|
+
start_time=start_dt,
|
|
1323
|
+
end_time=end_dt,
|
|
1324
|
+
status=status,
|
|
1325
|
+
notified_telegram=False,
|
|
1326
|
+
)
|
|
1327
|
+
new_count += 1
|
|
1328
|
+
|
|
1329
|
+
# Telegram 通知(僅近期且已完成的操作)
|
|
1330
|
+
should_notify = (
|
|
1331
|
+
task_type in NOTIFY_TYPES
|
|
1332
|
+
and status == "OK"
|
|
1333
|
+
and start_ts and start_ts >= notify_cutoff
|
|
1334
|
+
)
|
|
1335
|
+
if should_notify:
|
|
1336
|
+
label = TASK_LABELS.get(task_type, task_type)
|
|
1337
|
+
msg = (
|
|
1338
|
+
f"{label}\n\n"
|
|
1339
|
+
f"🖥️ VM: <b>{vm_name}</b> (VMID: {vmid})\n"
|
|
1340
|
+
f"👤 操作者: <code>{operator}</code>\n"
|
|
1341
|
+
f"📌 節點: <code>{node_name}</code>\n"
|
|
1342
|
+
f"🔀 叢集: {self.cluster_name}\n"
|
|
1343
|
+
f"📅 時間: {start_dt.strftime('%Y-%m-%d %H:%M:%S')}"
|
|
1344
|
+
)
|
|
1345
|
+
self.send_telegram_notification(msg)
|
|
1346
|
+
log.notified_telegram = True
|
|
1347
|
+
log.save(update_fields=["notified_telegram"])
|
|
1348
|
+
|
|
1349
|
+
if new_count > 0:
|
|
1350
|
+
print(f" 📋 新增 {new_count} 筆 VM 操作記錄")
|
|
1351
|
+
|
|
1238
1352
|
def detect_config_drift(self, vm_id: int, vm_name: str, vm_config: Dict[str, Any],
|
|
1239
1353
|
current_tags: List[str], network_interfaces: List[Dict] = None,
|
|
1240
1354
|
current_node: str = None) -> List[Dict]:
|
|
@@ -1576,14 +1690,18 @@ class OptimizedPVEToNetBoxSync:
|
|
|
1576
1690
|
# 但 state_db 仍有舊 hash 導致增量跳過。強制往下完整同步。
|
|
1577
1691
|
print(f" ⚠️ VM {original_vm_name} 不在 NetBox 中,忽略增量記錄強制建立")
|
|
1578
1692
|
else:
|
|
1579
|
-
# VM 存在;確保 device
|
|
1693
|
+
# VM 存在;確保 device 關聯、cluster 關聯、電源狀態都是最新的
|
|
1580
1694
|
current_device_id = getattr(getattr(cached_vm, 'device', None), 'id', None)
|
|
1581
1695
|
current_cluster_id = getattr(getattr(cached_vm, 'cluster', None), 'id', None)
|
|
1696
|
+
current_status = getattr(getattr(cached_vm, 'status', None), 'value', None) \
|
|
1697
|
+
or str(getattr(cached_vm, 'status', ''))
|
|
1582
1698
|
fix = {}
|
|
1583
1699
|
if current_device_id != device.id:
|
|
1584
1700
|
fix['device'] = device.id
|
|
1585
1701
|
if current_cluster_id != cluster['id']:
|
|
1586
1702
|
fix['cluster'] = cluster['id']
|
|
1703
|
+
if current_status != status:
|
|
1704
|
+
fix['status'] = status
|
|
1587
1705
|
if fix:
|
|
1588
1706
|
try:
|
|
1589
1707
|
cached_vm.update(fix)
|
|
@@ -1591,6 +1709,8 @@ class OptimizedPVEToNetBoxSync:
|
|
|
1591
1709
|
print(f" ✓ VM {original_vm_name} 叢集關聯已修正: → {cluster['name']}")
|
|
1592
1710
|
if 'device' in fix:
|
|
1593
1711
|
print(f" ✓ VM {original_vm_name} 設備關聯已修正: → {device.name}")
|
|
1712
|
+
if 'status' in fix:
|
|
1713
|
+
print(f" ✓ VM {original_vm_name} 電源狀態更新: {current_status} → {status}")
|
|
1594
1714
|
except Exception as e:
|
|
1595
1715
|
print(f" ✗ 更新 VM {original_vm_name} 關聯失敗: {e}")
|
|
1596
1716
|
print(f" ℹ️ VM {original_vm_name} 無配置變更,跳過同步")
|
|
@@ -1809,6 +1929,8 @@ class OptimizedPVEToNetBoxSync:
|
|
|
1809
1929
|
print("\n" + "=" * 50)
|
|
1810
1930
|
print("✓ 節點同步成功")
|
|
1811
1931
|
vms_success, success_count, total_count = self.sync_pve_virtual_machines(devices, cluster)
|
|
1932
|
+
print("\n同步 VM 操作記錄...")
|
|
1933
|
+
self.sync_vm_tasks()
|
|
1812
1934
|
elapsed = time.time() - start_time
|
|
1813
1935
|
self.stats['elapsed_time'] = elapsed
|
|
1814
1936
|
self.stats['total_vms'] = total_count
|
|
@@ -9,6 +9,7 @@ from pve_sync_plugin.models import (
|
|
|
9
9
|
PveClusterConfig,
|
|
10
10
|
PvePluginSettings,
|
|
11
11
|
PveSyncJob,
|
|
12
|
+
PveVmTaskLog,
|
|
12
13
|
PveWebhookEvent,
|
|
13
14
|
VmProvisioningLog,
|
|
14
15
|
)
|
|
@@ -177,3 +178,19 @@ class VmProvisioningLogSerializer(NetBoxModelSerializer):
|
|
|
177
178
|
"status", "notes", "checklist",
|
|
178
179
|
"created", "last_updated",
|
|
179
180
|
]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class PveVmTaskLogSerializer(NetBoxModelSerializer):
|
|
184
|
+
url = serializers.HyperlinkedIdentityField(
|
|
185
|
+
view_name="plugins-api:pve_sync_plugin-api:pvevmtasklog-detail",
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
class Meta:
|
|
189
|
+
model = PveVmTaskLog
|
|
190
|
+
fields = [
|
|
191
|
+
"id", "url", "display",
|
|
192
|
+
"upid", "vmid", "vm_name", "cluster_name", "node",
|
|
193
|
+
"task_type", "operator", "start_time", "end_time",
|
|
194
|
+
"status", "notified_telegram",
|
|
195
|
+
"created", "last_updated",
|
|
196
|
+
]
|
|
@@ -9,7 +9,7 @@ from netbox.api.routers import NetBoxRouter
|
|
|
9
9
|
from pve_sync_plugin import views as plugin_views
|
|
10
10
|
|
|
11
11
|
from . import views as api_views
|
|
12
|
-
from .views import VmProvisioningLogViewSet
|
|
12
|
+
from .views import PveVmTaskLogViewSet, VmProvisioningLogViewSet
|
|
13
13
|
|
|
14
14
|
app_name = "pve_sync_plugin-api"
|
|
15
15
|
|
|
@@ -22,6 +22,7 @@ router.register("clusters", api_views.PveClusterConfigViewSet)
|
|
|
22
22
|
router.register("backup-status", api_views.PveBackupStatusViewSet)
|
|
23
23
|
router.register("settings", api_views.PvePluginSettingsViewSet)
|
|
24
24
|
router.register("provisioning", api_views.VmProvisioningLogViewSet)
|
|
25
|
+
router.register("task-logs", api_views.PveVmTaskLogViewSet)
|
|
25
26
|
|
|
26
27
|
urlpatterns = [
|
|
27
28
|
path("webhook/", plugin_views.webhook_receiver, name="webhook"),
|
|
@@ -17,6 +17,7 @@ from pve_sync_plugin.models import (
|
|
|
17
17
|
PveClusterConfig,
|
|
18
18
|
PvePluginSettings,
|
|
19
19
|
PveSyncJob,
|
|
20
|
+
PveVmTaskLog,
|
|
20
21
|
PveWebhookEvent,
|
|
21
22
|
VmProvisioningLog,
|
|
22
23
|
)
|
|
@@ -26,6 +27,7 @@ from .serializers import (
|
|
|
26
27
|
PveClusterConfigSerializer,
|
|
27
28
|
PvePluginSettingsSerializer,
|
|
28
29
|
PveSyncJobSerializer,
|
|
30
|
+
PveVmTaskLogSerializer,
|
|
29
31
|
PveWebhookEventSerializer,
|
|
30
32
|
VmProvisioningLogSerializer,
|
|
31
33
|
)
|
|
@@ -99,3 +101,8 @@ class PvePluginSettingsViewSet(NetBoxModelViewSet):
|
|
|
99
101
|
class VmProvisioningLogViewSet(NetBoxModelViewSet):
|
|
100
102
|
queryset = VmProvisioningLog.objects.order_by("-created")
|
|
101
103
|
serializer_class = VmProvisioningLogSerializer
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class PveVmTaskLogViewSet(NetBoxModelViewSet):
|
|
107
|
+
queryset = PveVmTaskLog.objects.order_by("-start_time")
|
|
108
|
+
serializer_class = PveVmTaskLogSerializer
|
|
@@ -45,6 +45,19 @@ class SyncScheduleChoices(models.TextChoices):
|
|
|
45
45
|
WEEKLY = "weekly", "Weekly"
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
class VmTaskTypeChoices(models.TextChoices):
|
|
49
|
+
CREATE = "qmcreate", "建立 VM"
|
|
50
|
+
DESTROY = "qmdestroy", "刪除 VM"
|
|
51
|
+
CLONE = "qmclone", "Clone VM"
|
|
52
|
+
MIGRATE = "qmmigrate", "遷移 VM"
|
|
53
|
+
RESTORE = "qmrestore", "還原 VM"
|
|
54
|
+
BACKUP = "vzdump", "備份 VM"
|
|
55
|
+
START = "qmstart", "開機"
|
|
56
|
+
STOP = "qmstop", "強制關機"
|
|
57
|
+
SHUTDOWN = "qmshutdown", "正常關機"
|
|
58
|
+
REBOOT = "qmreboot", "重新開機"
|
|
59
|
+
|
|
60
|
+
|
|
48
61
|
class DriftTypeChoices(models.TextChoices):
|
|
49
62
|
HARDWARE = "hardware", "硬體配置變更"
|
|
50
63
|
MIGRATION = "migration", "VM 遷移"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Generated by Django 6.0.5 on 2026-06-22 16:57
|
|
2
|
+
|
|
3
|
+
import netbox.models.deletion
|
|
4
|
+
import taggit.managers
|
|
5
|
+
import utilities.json
|
|
6
|
+
from django.db import migrations, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
('extras', '0138_customfieldchoiceset_choice_colors'),
|
|
13
|
+
('pve_sync_plugin', '0014_vmprovisioninglog'),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.AddField(
|
|
18
|
+
model_name='vmprovisioninglog',
|
|
19
|
+
name='tags',
|
|
20
|
+
field=taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag'),
|
|
21
|
+
),
|
|
22
|
+
migrations.AlterField(
|
|
23
|
+
model_name='vmprovisioninglog',
|
|
24
|
+
name='custom_field_data',
|
|
25
|
+
field=models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder),
|
|
26
|
+
),
|
|
27
|
+
migrations.CreateModel(
|
|
28
|
+
name='PveVmTaskLog',
|
|
29
|
+
fields=[
|
|
30
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
|
31
|
+
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
|
32
|
+
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
|
33
|
+
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
|
|
34
|
+
('upid', models.CharField(max_length=300, unique=True)),
|
|
35
|
+
('vmid', models.IntegerField()),
|
|
36
|
+
('vm_name', models.CharField(blank=True, max_length=200)),
|
|
37
|
+
('cluster_name', models.CharField(max_length=100)),
|
|
38
|
+
('node', models.CharField(max_length=100)),
|
|
39
|
+
('task_type', models.CharField(max_length=30)),
|
|
40
|
+
('operator', models.CharField(max_length=200)),
|
|
41
|
+
('start_time', models.DateTimeField()),
|
|
42
|
+
('end_time', models.DateTimeField(blank=True, null=True)),
|
|
43
|
+
('status', models.CharField(blank=True, max_length=50)),
|
|
44
|
+
('notified_telegram', models.BooleanField(default=False)),
|
|
45
|
+
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
|
46
|
+
],
|
|
47
|
+
options={
|
|
48
|
+
'verbose_name': 'VM Task Log',
|
|
49
|
+
'verbose_name_plural': 'VM Task Logs',
|
|
50
|
+
'ordering': ['-start_time'],
|
|
51
|
+
'indexes': [models.Index(fields=['vmid', 'cluster_name'], name='pve_sync_pl_vmid_00b115_idx'), models.Index(fields=['task_type'], name='pve_sync_pl_task_ty_0f86cb_idx'), models.Index(fields=['operator'], name='pve_sync_pl_operato_6b34b5_idx'), models.Index(fields=['start_time'], name='pve_sync_pl_start_t_dac39f_idx')],
|
|
52
|
+
},
|
|
53
|
+
bases=(netbox.models.deletion.DeleteMixin, models.Model),
|
|
54
|
+
),
|
|
55
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generated by Django 6.0.5 on 2026-06-23 00:57
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('pve_sync_plugin', '0015_pvevmtasklog'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name='pvevmtasklog',
|
|
15
|
+
name='status',
|
|
16
|
+
field=models.TextField(blank=True),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -12,6 +12,7 @@ from .choices import (
|
|
|
12
12
|
SyncJobStatusChoices,
|
|
13
13
|
SyncJobTriggerChoices,
|
|
14
14
|
SyncScheduleChoices,
|
|
15
|
+
VmTaskTypeChoices,
|
|
15
16
|
WebhookEventChoices,
|
|
16
17
|
)
|
|
17
18
|
|
|
@@ -514,3 +515,59 @@ class VmProvisioningLog(NetBoxModel):
|
|
|
514
515
|
("chk_qemu_install", "安裝 qemu-guest-agent 套件"),
|
|
515
516
|
("chk_qemu_enable", "啟用並啟動 qemu-guest-agent 服務"),
|
|
516
517
|
]
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
class PveVmTaskLog(NetBoxModel):
|
|
521
|
+
"""Records VM lifecycle operations fetched from PVE task history."""
|
|
522
|
+
|
|
523
|
+
upid = models.CharField(max_length=300, unique=True, verbose_name="UPID")
|
|
524
|
+
vmid = models.IntegerField(verbose_name="VM ID")
|
|
525
|
+
vm_name = models.CharField(max_length=200, blank=True, verbose_name="VM 名稱")
|
|
526
|
+
cluster_name = models.CharField(max_length=100, verbose_name="叢集")
|
|
527
|
+
node = models.CharField(max_length=100, verbose_name="節點")
|
|
528
|
+
task_type = models.CharField(max_length=30, choices=VmTaskTypeChoices, verbose_name="操作類型")
|
|
529
|
+
operator = models.CharField(max_length=200, verbose_name="操作者")
|
|
530
|
+
start_time = models.DateTimeField(verbose_name="開始時間")
|
|
531
|
+
end_time = models.DateTimeField(null=True, blank=True, verbose_name="結束時間")
|
|
532
|
+
status = models.TextField(blank=True, verbose_name="狀態")
|
|
533
|
+
notified_telegram = models.BooleanField(default=False, verbose_name="已通知")
|
|
534
|
+
|
|
535
|
+
NOTIFY_TYPES = {"qmdestroy", "qmclone", "qmrestore"}
|
|
536
|
+
|
|
537
|
+
TASK_ICONS = {
|
|
538
|
+
"qmcreate": ("mdi mdi-plus-circle", "success"),
|
|
539
|
+
"qmdestroy": ("mdi mdi-delete", "danger"),
|
|
540
|
+
"qmclone": ("mdi mdi-content-copy", "info"),
|
|
541
|
+
"qmmigrate": ("mdi mdi-transfer", "warning"),
|
|
542
|
+
"qmrestore": ("mdi mdi-restore", "primary"),
|
|
543
|
+
"vzdump": ("mdi mdi-backup-restore", "secondary"),
|
|
544
|
+
"qmstart": ("mdi mdi-play-circle", "success"),
|
|
545
|
+
"qmstop": ("mdi mdi-stop-circle", "danger"),
|
|
546
|
+
"qmshutdown": ("mdi mdi-power", "warning"),
|
|
547
|
+
"qmreboot": ("mdi mdi-restart", "info"),
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
class Meta:
|
|
551
|
+
ordering = ["-start_time"]
|
|
552
|
+
indexes = [
|
|
553
|
+
models.Index(fields=["vmid", "cluster_name"]),
|
|
554
|
+
models.Index(fields=["task_type"]),
|
|
555
|
+
models.Index(fields=["operator"]),
|
|
556
|
+
models.Index(fields=["start_time"]),
|
|
557
|
+
]
|
|
558
|
+
verbose_name = "VM Task Log"
|
|
559
|
+
verbose_name_plural = "VM Task Logs"
|
|
560
|
+
|
|
561
|
+
def __str__(self):
|
|
562
|
+
return f"{self.get_task_type_display()} — {self.vm_name} (VMID {self.vmid}) by {self.operator}"
|
|
563
|
+
|
|
564
|
+
def get_absolute_url(self):
|
|
565
|
+
return reverse("plugins:pve_sync_plugin:pvevmtasklog_list")
|
|
566
|
+
|
|
567
|
+
@property
|
|
568
|
+
def task_icon(self):
|
|
569
|
+
return self.TASK_ICONS.get(self.task_type, ("mdi mdi-history", "secondary"))
|
|
570
|
+
|
|
571
|
+
@property
|
|
572
|
+
def status_ok(self):
|
|
573
|
+
return self.status == "OK"
|
|
@@ -75,6 +75,11 @@ vm_provisioning_item = PluginMenuItem(
|
|
|
75
75
|
link_text="VM 佈建",
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
+
vm_task_log_item = PluginMenuItem(
|
|
79
|
+
link="plugins:pve_sync_plugin:pvevmtasklog_list",
|
|
80
|
+
link_text="VM 操作記錄",
|
|
81
|
+
)
|
|
82
|
+
|
|
78
83
|
menu = PluginMenu(
|
|
79
84
|
label="Proxmox Sync",
|
|
80
85
|
groups=(
|
|
@@ -92,7 +97,7 @@ menu = PluginMenu(
|
|
|
92
97
|
),
|
|
93
98
|
(
|
|
94
99
|
"VM Operations",
|
|
95
|
-
(vm_provisioning_item,),
|
|
100
|
+
(vm_provisioning_item, vm_task_log_item),
|
|
96
101
|
),
|
|
97
102
|
(
|
|
98
103
|
"Configuration",
|
|
@@ -10,6 +10,7 @@ from .models import (
|
|
|
10
10
|
PveClusterConfig,
|
|
11
11
|
PveDriftEvent,
|
|
12
12
|
PveSyncJob,
|
|
13
|
+
PveVmTaskLog,
|
|
13
14
|
PveWebhookEvent,
|
|
14
15
|
VmProvisioningLog,
|
|
15
16
|
)
|
|
@@ -299,3 +300,27 @@ class VmProvisioningLogTable(NetBoxTable):
|
|
|
299
300
|
"management_ip", "internet_ip", "created")
|
|
300
301
|
default_columns = ("vm_name", "vmid", "cluster_name", "node", "status",
|
|
301
302
|
"management_ip", "internet_ip")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
# PveVmTaskLog
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
class PveVmTaskLogTable(NetBoxTable):
|
|
310
|
+
vm_name = tables.Column(verbose_name="VM 名稱")
|
|
311
|
+
vmid = tables.Column(verbose_name="VM ID")
|
|
312
|
+
cluster_name = tables.Column(verbose_name="叢集")
|
|
313
|
+
node = tables.Column(verbose_name="節點")
|
|
314
|
+
task_type = columns.ChoiceFieldColumn(verbose_name="操作類型")
|
|
315
|
+
operator = tables.Column(verbose_name="操作者")
|
|
316
|
+
start_time = columns.DateTimeColumn(verbose_name="時間")
|
|
317
|
+
status = tables.Column(verbose_name="結果")
|
|
318
|
+
notified_telegram = columns.BooleanColumn(verbose_name="已通知")
|
|
319
|
+
actions = columns.ActionsColumn(actions=("delete",))
|
|
320
|
+
|
|
321
|
+
class Meta(NetBoxTable.Meta):
|
|
322
|
+
model = PveVmTaskLog
|
|
323
|
+
fields = ("pk", "vm_name", "vmid", "cluster_name", "node",
|
|
324
|
+
"task_type", "operator", "start_time", "status", "notified_telegram")
|
|
325
|
+
default_columns = ("vm_name", "vmid", "cluster_name", "node",
|
|
326
|
+
"task_type", "operator", "start_time", "status")
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from netbox.plugins import PluginTemplateExtension
|
|
4
4
|
|
|
5
|
-
from .models import PveClusterConfig, PveSyncJob
|
|
5
|
+
from .models import PveClusterConfig, PveSyncJob, PveVmTaskLog
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class VirtualMachineSyncButton(PluginTemplateExtension):
|
|
@@ -41,4 +41,37 @@ class VirtualMachineSyncButton(PluginTemplateExtension):
|
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
class VmTaskLogPanel(PluginTemplateExtension):
|
|
45
|
+
"""Inject a VM operation history panel into NetBox VM detail pages."""
|
|
46
|
+
|
|
47
|
+
models = ["virtualization.virtualmachine"]
|
|
48
|
+
|
|
49
|
+
def full_width_page(self):
|
|
50
|
+
vm = self.context["object"]
|
|
51
|
+
|
|
52
|
+
if not vm.serial:
|
|
53
|
+
return ""
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
vmid = int(vm.serial)
|
|
57
|
+
except (ValueError, TypeError):
|
|
58
|
+
return ""
|
|
59
|
+
|
|
60
|
+
qs = PveVmTaskLog.objects.filter(vmid=vmid)
|
|
61
|
+
cluster_config = None
|
|
62
|
+
if vm.cluster_id:
|
|
63
|
+
cluster_config = PveClusterConfig.objects.filter(
|
|
64
|
+
netbox_cluster_id=vm.cluster_id, enabled=True,
|
|
65
|
+
).first()
|
|
66
|
+
if cluster_config:
|
|
67
|
+
qs = qs.filter(cluster_name=cluster_config.name)
|
|
68
|
+
|
|
69
|
+
task_logs = qs.order_by("-start_time")[:30]
|
|
70
|
+
|
|
71
|
+
return self.render(
|
|
72
|
+
"pve_sync/inc/vm_task_log_panel.html",
|
|
73
|
+
extra_context={"task_logs": task_logs, "vmid": vmid},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
template_extensions = [VirtualMachineSyncButton, VmTaskLogPanel]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{% load helpers %}
|
|
2
|
+
|
|
3
|
+
<div class="card mt-3">
|
|
4
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
5
|
+
<h5 class="mb-0"><i class="mdi mdi-history me-2"></i>PVE 操作記錄</h5>
|
|
6
|
+
<a href="{% url 'plugins:pve_sync_plugin:pvevmtasklog_list' %}?vmid={{ vmid }}"
|
|
7
|
+
class="btn btn-sm btn-outline-secondary">查看全部</a>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="card-body p-0">
|
|
10
|
+
{% if task_logs %}
|
|
11
|
+
<table class="table table-hover table-sm mb-0">
|
|
12
|
+
<thead class="table-secondary">
|
|
13
|
+
<tr>
|
|
14
|
+
<th>操作類型</th>
|
|
15
|
+
<th>操作者</th>
|
|
16
|
+
<th>節點</th>
|
|
17
|
+
<th>時間</th>
|
|
18
|
+
<th>結果</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
{% for log in task_logs %}
|
|
23
|
+
<tr>
|
|
24
|
+
<td>
|
|
25
|
+
{% with log.task_icon as icon %}
|
|
26
|
+
<i class="{{ icon.0 }} text-{{ icon.1 }} me-1"></i>
|
|
27
|
+
{{ log.get_task_type_display }}
|
|
28
|
+
{% endwith %}
|
|
29
|
+
</td>
|
|
30
|
+
<td><code>{{ log.operator }}</code></td>
|
|
31
|
+
<td>{{ log.node }}</td>
|
|
32
|
+
<td>
|
|
33
|
+
<span title="{{ log.start_time }}">
|
|
34
|
+
{{ log.start_time|date:"Y-m-d H:i:s" }}
|
|
35
|
+
</span>
|
|
36
|
+
</td>
|
|
37
|
+
<td>
|
|
38
|
+
{% if log.status_ok %}
|
|
39
|
+
<span class="badge bg-success">OK</span>
|
|
40
|
+
{% elif log.status %}
|
|
41
|
+
<span class="badge bg-danger" title="{{ log.status }}">ERROR</span>
|
|
42
|
+
{% else %}
|
|
43
|
+
<span class="badge bg-secondary">—</span>
|
|
44
|
+
{% endif %}
|
|
45
|
+
</td>
|
|
46
|
+
</tr>
|
|
47
|
+
{% endfor %}
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
{% else %}
|
|
51
|
+
<div class="text-center text-muted py-4">
|
|
52
|
+
<i class="mdi mdi-history mdi-48px d-block mb-2"></i>
|
|
53
|
+
尚無操作記錄(下次同步後自動載入)
|
|
54
|
+
</div>
|
|
55
|
+
{% endif %}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{% extends 'generic/object_list.html' %}
|
|
2
|
+
{% load helpers %}
|
|
3
|
+
|
|
4
|
+
{% block title %}VM 操作記錄{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block extra_controls %}
|
|
7
|
+
<div class="d-flex gap-2 align-items-center">
|
|
8
|
+
<span class="text-muted small">由 PVE Task History API 自動同步</span>
|
|
9
|
+
</div>
|
|
10
|
+
{% endblock %}
|
|
@@ -10,7 +10,7 @@ from django.urls import path
|
|
|
10
10
|
from netbox.views.generic import ObjectChangeLogView
|
|
11
11
|
|
|
12
12
|
from . import views
|
|
13
|
-
from .models import PbsServerConfig, PveBackupStatus, PveClusterConfig, PveDriftEvent, PveSyncJob, PveWebhookEvent, VmProvisioningLog
|
|
13
|
+
from .models import PbsServerConfig, PveBackupStatus, PveClusterConfig, PveDriftEvent, PveSyncJob, PveVmTaskLog, PveWebhookEvent, VmProvisioningLog
|
|
14
14
|
|
|
15
15
|
app_name = "pve_sync_plugin"
|
|
16
16
|
|
|
@@ -181,6 +181,13 @@ urlpatterns = [
|
|
|
181
181
|
kwargs={"model": PveDriftEvent},
|
|
182
182
|
),
|
|
183
183
|
|
|
184
|
+
# --- PveVmTaskLog ---
|
|
185
|
+
path("task-logs/", views.PveVmTaskLogListView.as_view(), name="pvevmtasklog_list"),
|
|
186
|
+
path("task-logs/<int:pk>/delete/", views.PveVmTaskLogDeleteView.as_view(), name="pvevmtasklog_delete"),
|
|
187
|
+
path("task-logs/delete/", views.PveVmTaskLogBulkDeleteView.as_view(), name="pvevmtasklog_bulk_delete"),
|
|
188
|
+
path("task-logs/<int:pk>/changelog/", ObjectChangeLogView.as_view(),
|
|
189
|
+
name="pvevmtasklog_changelog", kwargs={"model": PveVmTaskLog}),
|
|
190
|
+
|
|
184
191
|
# IP probe APIs (used by provisioning form)
|
|
185
192
|
path("provisioning/free-ips/<int:range_id>/", views.VmPlannerFreeIpsApi.as_view(), name="vm-planner-free-ips"),
|
|
186
193
|
path("provisioning/check-ip/", views.VmPlannerCheckIpApi.as_view(), name="vm-planner-check-ip"),
|
|
@@ -49,6 +49,7 @@ from .models import (
|
|
|
49
49
|
PveDriftEvent,
|
|
50
50
|
PvePluginSettings,
|
|
51
51
|
PveSyncJob,
|
|
52
|
+
PveVmTaskLog,
|
|
52
53
|
PveWebhookEvent,
|
|
53
54
|
VmProvisioningLog,
|
|
54
55
|
)
|
|
@@ -58,6 +59,7 @@ from .tables import (
|
|
|
58
59
|
PveClusterConfigTable,
|
|
59
60
|
PveDriftEventTable,
|
|
60
61
|
PveSyncJobTable,
|
|
62
|
+
PveVmTaskLogTable,
|
|
61
63
|
PveWebhookEventTable,
|
|
62
64
|
VmProvisioningLogTable,
|
|
63
65
|
)
|
|
@@ -342,6 +344,25 @@ class PveDriftEventBulkDeleteView(generic.BulkDeleteView):
|
|
|
342
344
|
table = PveDriftEventTable
|
|
343
345
|
|
|
344
346
|
|
|
347
|
+
# ============================================================================
|
|
348
|
+
# PveVmTaskLog — read-only list from PVE task history
|
|
349
|
+
# ============================================================================
|
|
350
|
+
|
|
351
|
+
class PveVmTaskLogListView(generic.ObjectListView):
|
|
352
|
+
queryset = PveVmTaskLog.objects.order_by("-start_time")
|
|
353
|
+
table = PveVmTaskLogTable
|
|
354
|
+
template_name = "pve_sync/pvevmtasklog_list.html"
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class PveVmTaskLogDeleteView(generic.ObjectDeleteView):
|
|
358
|
+
queryset = PveVmTaskLog.objects.all()
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class PveVmTaskLogBulkDeleteView(generic.BulkDeleteView):
|
|
362
|
+
queryset = PveVmTaskLog.objects.all()
|
|
363
|
+
table = PveVmTaskLogTable
|
|
364
|
+
|
|
365
|
+
|
|
345
366
|
# ============================================================================
|
|
346
367
|
# Plugin Settings (singleton)
|
|
347
368
|
# ============================================================================
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/management/commands/run_scheduled_syncs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0005_align_netboxmodel_timestamps.py
RENAMED
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0006_pbsserverconfig_and_more.py
RENAMED
|
File without changes
|
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0008_pbsserverconfig_sync_schedule.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0011_pveclusterconfig_notify_on_sync.py
RENAMED
|
File without changes
|
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/migrations/0013_pluginsettings_log_retention.py
RENAMED
|
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
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/inc/vm_sync_button.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/vm_provisioning_combined.html
RENAMED
|
File without changes
|
{proxsync-2.0.2 → proxsync-2.0.3}/pve_sync_plugin/templates/pve_sync/vm_provisioning_log.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|