diff --git a/README.md b/README.md index 98759fe..f87b650 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ Ein in NetBox integriertes Markdown-Wiki für Betriebsdokumentationen und Anleit - Tabellen-Presets, farbige Kopfzeilen und Zellhervorhebungen im WYSIWYG-Editor - Excel-Mehrblattimport: je Arbeitsblatt eine Dokumentation in einem gewählten Zielordner - Zweistufige Excel-Vorschau mit Blattauswahl und frei änderbaren Dokumenttiteln -- Benutzergebundene Bearbeitungssperren mit Heartbeat und zusätzlicher Versionskonfliktprüfung - Optionales Glätten von Excel-Tabellen in kompakte Feld-/Wert-Textblöcke - Eine Dokumentation mehreren Objekten zuordnen und umgekehrt - Unbegrenzt viele Objektzuordnungen pro Dokumentation; nur identische Doppelzuordnungen werden verhindert @@ -29,7 +28,7 @@ Ein in NetBox integriertes Markdown-Wiki für Betriebsdokumentationen und Anleit ## Kompatibilität -Die Version `0.8.0` zielt auf NetBox 4.x (mindestens 4.0). Vor einem produktiven Rollout sollte das Plugin gegen die konkret eingesetzte NetBox-Minor-Version in einer Testinstanz geprüft werden. +Die Version `0.7.0` zielt auf NetBox 4.x (mindestens 4.0). Vor einem produktiven Rollout sollte das Plugin gegen die konkret eingesetzte NetBox-Minor-Version in einer Testinstanz geprüft werden. ## Installation @@ -78,7 +77,6 @@ PLUGINS_CONFIG = { "netbox_documentation": { "max_import_size_mb": 25, "max_archive_size_mb": 250, - "edit_lock_timeout_minutes": 5, "keep_imported_file": True, "allowed_object_types": [ "dcim.region", @@ -94,12 +92,6 @@ PLUGINS_CONFIG = { } ``` -### Gleichzeitiges Bearbeiten - -Beim Öffnen einer bestehenden Dokumentation im Editor wird eine benutzergebundene Bearbeitungssperre gesetzt und während der Arbeit regelmäßig verlängert. Andere Benutzer sehen den aktiven Bearbeiter und können erst nach Freigabe oder Ablauf der Sperre in den Editor wechseln. `edit_lock_timeout_minutes` bestimmt die Zeit ohne Heartbeat, nach der eine abgebrochene Sitzung automatisch übernommen werden darf. - -Zusätzlich wird beim Speichern die ursprünglich geöffnete Version mit dem aktuellen Datenbankstand verglichen. Wurde die Dokumentation zwischenzeitlich verändert, bricht das Plugin den Speichervorgang ab, zeigt den nicht gespeicherten Inhalt zum Kopieren an und überschreibt die neuere Fassung nicht. - TinyMCE wird als Python-Abhängigkeit des Plugins installiert und über einen lokalen, gecachten Plugin-Endpunkt bereitgestellt. `collectstatic` übernimmt die Dateien zusätzlich in NetBox. Der Editor benötigt deshalb weder Zugriff auf ein CDN noch einen API-Key. Das verwendete TinyMCE wird im GPL-Modus betrieben. Kann das Script nicht geladen werden, bleibt als Rückfall ein normales HTML-Textfeld verfügbar. Danach die Migrationen und statischen Dateien aktualisieren und NetBox neu starten: diff --git a/netbox_documentation/__init__.py b/netbox_documentation/__init__.py index e342765..58c5b99 100644 --- a/netbox_documentation/__init__.py +++ b/netbox_documentation/__init__.py @@ -5,7 +5,7 @@ class DocumentationConfig(PluginConfig): name = "netbox_documentation" verbose_name = "NetBox Dokumentation" description = "Wiki und Office-Dokumentation direkt in NetBox" - version = "0.8.0" + version = "0.7.0" author = "LKE" base_url = "documentation" min_version = "4.0.0" @@ -18,7 +18,6 @@ class DocumentationConfig(PluginConfig): ], "max_import_size_mb": 25, "max_archive_size_mb": 250, - "edit_lock_timeout_minutes": 5, "keep_imported_file": True, } diff --git a/netbox_documentation/forms.py b/netbox_documentation/forms.py index cd29b51..8d8ea7e 100644 --- a/netbox_documentation/forms.py +++ b/netbox_documentation/forms.py @@ -39,8 +39,6 @@ class DocumentForm(NetBoxModelForm): "rows": 32, "class": "rich-text-editor", "data-rich-text-editor": "true" }), help_text="Formatierter Text mit Tabellen, Farben, Schriftarten und Größen.") body_format = forms.CharField(widget=forms.HiddenInput(), initial="html") - edit_lock_token = forms.CharField(required=False, widget=forms.HiddenInput()) - expected_version = forms.CharField(required=False, widget=forms.HiddenInput()) category = DynamicModelChoiceField(queryset=DocumentCategory.objects.all(), required=False, label="Ordner") fieldsets = ( diff --git a/netbox_documentation/migrations/0005_documenteditlock.py b/netbox_documentation/migrations/0005_documenteditlock.py deleted file mode 100644 index 2fcba10..0000000 --- a/netbox_documentation/migrations/0005_documenteditlock.py +++ /dev/null @@ -1,25 +0,0 @@ -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("netbox_documentation", "0004_excelimportpreview"), - ] - - operations = [ - migrations.CreateModel( - name="DocumentEditLock", - fields=[ - ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), - ("token", models.UUIDField(default=uuid.uuid4, editable=False)), - ("acquired", models.DateTimeField(auto_now_add=True)), - ("heartbeat", models.DateTimeField(auto_now=True)), - ("document", models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name="edit_lock", to="netbox_documentation.document")), - ("user", models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/netbox_documentation/models.py b/netbox_documentation/models.py index 8e20023..64bd137 100644 --- a/netbox_documentation/models.py +++ b/netbox_documentation/models.py @@ -169,17 +169,3 @@ class ExcelImportPreview(models.Model): def __str__(self): return self.original_name - - -class DocumentEditLock(models.Model): - """Short-lived advisory lock; optimistic version checks remain authoritative.""" - _netbox_private = True - - document = models.OneToOneField(Document, on_delete=models.SET_NULL, null=True, related_name="edit_lock") - user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True) - token = models.UUIDField(default=uuid.uuid4, editable=False) - acquired = models.DateTimeField(auto_now_add=True) - heartbeat = models.DateTimeField(auto_now=True) - - def __str__(self): - return f"{self.document} – {self.user}" diff --git a/netbox_documentation/templates/netbox_documentation/document_edit.html b/netbox_documentation/templates/netbox_documentation/document_edit.html index dfebd8a..c4bf02c 100644 --- a/netbox_documentation/templates/netbox_documentation/document_edit.html +++ b/netbox_documentation/templates/netbox_documentation/document_edit.html @@ -135,48 +135,12 @@ }); } }); - - {% if object.pk %} - const lockToken = document.getElementById('id_edit_lock_token'); - const editForm = document.querySelector('form.object-edit'); - const csrfToken = document.querySelector('input[name=csrfmiddlewaretoken]'); - const lockUrl = "{% url 'plugins:netbox_documentation:document_lock' pk=object.pk %}"; - let submitting = false; - const heartbeat = () => { - if (!lockToken || !lockToken.value || submitting) return; - const data = new URLSearchParams({token: lockToken.value, action: 'heartbeat'}); - fetch(lockUrl, { - method: 'POST', credentials: 'same-origin', body: data, - headers: {'X-CSRFToken': csrfToken.value, 'Content-Type': 'application/x-www-form-urlencoded'} - }).then(response => { - if (response.status === 409) { - const warning = document.createElement('div'); - warning.className = 'alert alert-danger position-fixed top-0 start-50 translate-middle-x mt-3'; - warning.style.zIndex = '2000'; - warning.textContent = 'Die Bearbeitungssperre ist abgelaufen oder wurde übernommen. Bitte Inhalt sichern und die Seite neu laden.'; - document.body.appendChild(warning); - } - }); - }; - const heartbeatTimer = window.setInterval(heartbeat, 60000); - if (editForm) editForm.addEventListener('submit', () => { submitting = true; window.clearInterval(heartbeatTimer); }); - window.addEventListener('beforeunload', () => { - if (submitting || !lockToken || !lockToken.value) return; - const data = new FormData(); - data.append('csrfmiddlewaretoken', csrfToken.value); - data.append('token', lockToken.value); - data.append('action', 'release'); - navigator.sendBeacon(lockUrl, data); - }); - {% endif %} }); {% endblock javascript %} {% block pre_form_fields %} - {% if object.pk %} -
Die Dokumentation wurde seit dem Öffnen verändert. Deine Fassung wurde nicht gespeichert und die neuere Version wurde nicht überschrieben.
-Titel: {{ submitted_title }}
- -Diesen Inhalt kannst du kopieren und nach Prüfung in die aktuelle Version übernehmen.
-Deine Bearbeitungssperre ist nicht mehr gültig. Der Inhalt wurde nicht überschrieben.
- {% elif lock.user %} -{{ lock.user }} bearbeitet „{{ object.title }}“ momentan.
- {% else %} -Diese Dokumentation besitzt momentan eine aktive Bearbeitungssperre.
- {% endif %} -Ohne Heartbeat läuft eine Sperre nach {{ timeout }} Minuten automatisch ab.
- -