fix: TinyMCE über lokalen Plugin-Endpunkt ausliefern
This commit is contained in:
@@ -15,7 +15,7 @@ Ein in NetBox integriertes Markdown-Wiki für Betriebsdokumentationen und Anleit
|
|||||||
|
|
||||||
## Kompatibilität
|
## Kompatibilität
|
||||||
|
|
||||||
Die Version `0.2.1` 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.2.2` 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
|
## Installation
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ PLUGINS_CONFIG = {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
TinyMCE wird als Python-Abhängigkeit des Plugins installiert und durch `collectstatic` lokal bereitgestellt. 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.
|
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:
|
Danach die Migrationen und statischen Dateien aktualisieren und NetBox neu starten:
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class DocumentationConfig(PluginConfig):
|
|||||||
name = "netbox_documentation"
|
name = "netbox_documentation"
|
||||||
verbose_name = "Dokumentation"
|
verbose_name = "Dokumentation"
|
||||||
description = "Wiki und Office-Dokumentation direkt in NetBox"
|
description = "Wiki und Office-Dokumentation direkt in NetBox"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
author = "NetBox Documentation Contributors"
|
author = "NetBox Documentation Contributors"
|
||||||
base_url = "documentation"
|
base_url = "documentation"
|
||||||
min_version = "4.0.0"
|
min_version = "4.0.0"
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
{% extends 'generic/object_edit.html' %}
|
{% extends 'generic/object_edit.html' %}
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block javascript %}
|
{% block javascript %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<script src="{% static 'tinymce/tinymce.min.js' %}"></script>
|
<script src="{% url 'plugins:netbox_documentation:editor_asset' asset_path='tinymce.min.js' %}"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const editorAssetBase = "{% url 'plugins:netbox_documentation:editor_asset' asset_path='_' %}".replace(/_$/, '');
|
||||||
if (typeof tinymce === 'undefined') {
|
if (typeof tinymce === 'undefined') {
|
||||||
const editor = document.querySelector('[data-rich-text-editor]');
|
const editor = document.querySelector('[data-rich-text-editor]');
|
||||||
if (editor) {
|
if (editor) {
|
||||||
const warning = document.createElement('div');
|
const warning = document.createElement('div');
|
||||||
warning.className = 'alert alert-warning';
|
warning.className = 'alert alert-warning';
|
||||||
warning.textContent = 'Der Rich-Text-Editor konnte nicht geladen werden. Der Inhalt kann als HTML bearbeitet werden.';
|
warning.textContent = 'Der Rich-Text-Editor konnte nicht vom lokalen Plugin-Endpunkt geladen werden. Bitte Browser-Konsole und Netzwerkprotokoll prüfen.';
|
||||||
editor.parentNode.insertBefore(warning, editor);
|
editor.parentNode.insertBefore(warning, editor);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
tinymce.init({
|
tinymce.init({
|
||||||
selector: 'textarea[data-rich-text-editor]',
|
selector: 'textarea[data-rich-text-editor]',
|
||||||
|
base_url: editorAssetBase.replace(/\/$/, ''),
|
||||||
|
suffix: '.min',
|
||||||
license_key: 'gpl',
|
license_key: 'gpl',
|
||||||
height: 650,
|
height: 650,
|
||||||
menubar: 'file edit view insert format tools table help',
|
menubar: 'file edit view insert format tools table help',
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from netbox.views.generic import ObjectChangeLogView
|
|||||||
from . import models, views
|
from . import models, views
|
||||||
|
|
||||||
urlpatterns = (
|
urlpatterns = (
|
||||||
|
path("editor-assets/<path:asset_path>", views.EditorAssetView.as_view(), name="editor_asset"),
|
||||||
path("", views.DocumentListView.as_view(), name="document_list"),
|
path("", views.DocumentListView.as_view(), name="document_list"),
|
||||||
path("documents/add/", views.DocumentEditView.as_view(), name="document_add"),
|
path("documents/add/", views.DocumentEditView.as_view(), name="document_add"),
|
||||||
path("documents/import/", views.DocumentImportView.as_view(), name="document_import"),
|
path("documents/import/", views.DocumentImportView.as_view(), name="document_import"),
|
||||||
@@ -15,4 +16,3 @@ urlpatterns = (
|
|||||||
path("assignments/<int:pk>/edit/", views.AssignmentEditView.as_view(), name="documentassignment_edit"),
|
path("assignments/<int:pk>/edit/", views.AssignmentEditView.as_view(), name="documentassignment_edit"),
|
||||||
path("assignments/<int:pk>/delete/", views.AssignmentDeleteView.as_view(), name="documentassignment_delete"),
|
path("assignments/<int:pk>/delete/", views.AssignmentDeleteView.as_view(), name="documentassignment_delete"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import mimetypes
|
||||||
|
import tinymce
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
from django.http import FileResponse, Http404
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.views import View
|
from django.views import View
|
||||||
@@ -14,6 +17,21 @@ from .models import Document, DocumentAssignment, DocumentAttachment
|
|||||||
from .tables import DocumentTable, AssignmentTable
|
from .tables import DocumentTable, AssignmentTable
|
||||||
|
|
||||||
|
|
||||||
|
class EditorAssetView(View):
|
||||||
|
"""Serve the bundled editor assets when NetBox's static proxy is unavailable."""
|
||||||
|
|
||||||
|
def get(self, request, asset_path):
|
||||||
|
root = (Path(tinymce.__file__).resolve().parent / "static" / "tinymce").resolve()
|
||||||
|
candidate = (root / asset_path).resolve()
|
||||||
|
if root not in candidate.parents or not candidate.is_file():
|
||||||
|
raise Http404
|
||||||
|
content_type = mimetypes.guess_type(candidate.name)[0] or "application/octet-stream"
|
||||||
|
response = FileResponse(candidate.open("rb"), content_type=content_type)
|
||||||
|
response["Cache-Control"] = "public, max-age=31536000, immutable"
|
||||||
|
response["X-Content-Type-Options"] = "nosniff"
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class DocumentListView(generic.ObjectListView):
|
class DocumentListView(generic.ObjectListView):
|
||||||
queryset = Document.objects.prefetch_related("assignments")
|
queryset = Document.objects.prefetch_related("assignments")
|
||||||
table = DocumentTable
|
table = DocumentTable
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "netbox-documentation"
|
name = "netbox-documentation"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
description = "Integrated Markdown wiki and office document importer for NetBox"
|
description = "Integrated Markdown wiki and office document importer for NetBox"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
|
|||||||
Reference in New Issue
Block a user