Compare commits

..

5 Commits

14 changed files with 268 additions and 380 deletions

View File

@@ -0,0 +1,102 @@
/* Better visual separation for Vorgaben inlines */
.inline-group[data-inline-model="vorgabe"] {
border: 2px solid #ddd;
border-radius: 8px;
margin-bottom: 20px;
padding: 15px;
background-color: #f9f9f9;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.inline-group[data-inline-model="vorgabe"] .inline-related {
border: 1px solid #ccc;
border-radius: 6px;
margin-bottom: 10px;
background-color: white;
padding: 10px;
}
.inline-group[data-inline-model="vorgabe"] h3 {
background-color: #007cba;
color: white;
padding: 8px 12px;
margin: -15px -15px 10px -15px;
border-radius: 6px 6px 0 0;
font-weight: bold;
}
.inline-group[data-inline-model="vorgabe"] .collapse .inline-related {
border-left: 3px solid #007cba;
}
/* Better spacing for nested inlines */
.inline-group[data-inline-model="vorgabe"] .inline-group {
margin-top: 10px;
}
.inline-group[data-inline-model="vorgabe"] .inline-group h3 {
background-color: #f0f8ff;
color: #333;
padding: 6px 10px;
margin: 0 0 8px 0;
border-left: 3px solid #007cba;
}
/* Highlight active/expanded vorgabe */
.inline-group[data-inline-model="vorgabe"] .inline-related:not(.collapsed) {
border-color: #007cba;
box-shadow: 0 0 8px rgba(0,124,186,0.2);
}
/* Highlight actively edited vorgabe */
.inline-group[data-inline-model="vorgabe"] .inline-related.active-edit {
border-color: #28a745;
box-shadow: 0 0 12px rgba(40,167,69,0.3);
background-color: #f8fff9;
}
/* Toggle hint styling */
.toggle-hint {
font-size: 0.8em;
color: #666;
font-weight: normal;
}
/* Better fieldset styling for vorgabe inlines */
.inline-group[data-inline-model="vorgabe"] .fieldset {
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 10px;
margin-bottom: 10px;
background-color: #fafafa;
}
.inline-group[data-inline-model="vorgabe"] .fieldset h2 {
background-color: #e3f2fd;
color: #1565c0;
padding: 5px 10px;
margin: -10px -10px 10px -10px;
border-radius: 4px 4px 0 0;
font-size: 0.9em;
font-weight: bold;
}
/* Better form layout */
.inline-group[data-inline-model="vorgabe"] .form-row {
border-bottom: 1px solid #eee;
padding: 8px 0;
}
.inline-group[data-inline-model="vorgabe"] .form-row:last-child {
border-bottom: none;
}
/* Wide fields styling */
.inline-group[data-inline-model="vorgabe"] .wide .form-row > div {
width: 100%;
}
.inline-group[data-inline-model="vorgabe"] .wide textarea {
width: 100%;
min-height: 80px;
}

View File

@@ -0,0 +1,25 @@
(function($) {
'use strict';
$(document).ready(function() {
// Add toggle buttons for each vorgabe inline
$('.inline-group[data-inline-model="vorgabe"]').each(function() {
var $group = $(this);
var $headers = $group.find('h3');
$headers.css('cursor', 'pointer').append(' <span class="toggle-hint">(klicken zum umschalten)</span>');
$headers.on('click', function(e) {
e.preventDefault();
var $inline = $(this).closest('.inline-related');
$inline.find('.collapse').toggleClass('collapsed');
});
});
// Highlight active vorgabe when editing
$('.inline-group[data-inline-model="vorgabe"] .inline-related').on('click', function() {
$('.inline-group[data-inline-model="vorgabe"] .inline-related').removeClass('active-edit');
$(this).addClass('active-edit');
});
});
})(django.jQuery);

View File

@@ -21,45 +21,75 @@ from referenzen.models import Referenz
# 'frage': forms.Textarea(attrs={'rows': 1, 'cols': 100}), # 'frage': forms.Textarea(attrs={'rows': 1, 'cols': 100}),
# } # }
class ChecklistenfragenInline(NestedTabularInline): class ChecklistenfragenInline(NestedStackedInline):
model=Checklistenfrage model=Checklistenfrage
extra=0 extra=0
fk_name="vorgabe" fk_name="vorgabe"
# form=ChecklistenForm
classes = ['collapse'] classes = ['collapse']
verbose_name_plural = "Checklistenfragen"
fieldsets = (
(None, {
'fields': ('frage',),
'classes': ('wide',),
}),
)
class VorgabeKurztextInline(NestedTabularInline): class VorgabeKurztextInline(NestedStackedInline):
model=VorgabeKurztext model=VorgabeKurztext
extra=0 extra=0
sortable_field_name = "order" sortable_field_name = "order"
show_change_link=True show_change_link=True
classes = ['collapse'] classes = ['collapse']
#inline=inhalt verbose_name_plural = "Kurztext-Abschnitte"
fieldsets = (
(None, {
'fields': ('abschnitttyp', 'inhalt', 'order'),
'classes': ('wide',),
}),
)
class VorgabeLangtextInline(NestedTabularInline): class VorgabeLangtextInline(NestedStackedInline):
model=VorgabeLangtext model=VorgabeLangtext
extra=0 extra=0
sortable_field_name = "order" sortable_field_name = "order"
show_change_link=True show_change_link=True
classes = ['collapse'] classes = ['collapse']
#inline=inhalt verbose_name_plural = "Langtext-Abschnitte"
fieldsets = (
(None, {
'fields': ('abschnitttyp', 'inhalt', 'order'),
'classes': ('wide',),
}),
)
class GeltungsbereichInline(NestedTabularInline): class GeltungsbereichInline(NestedStackedInline):
model=Geltungsbereich model=Geltungsbereich
extra=0 extra=0
sortable_field_name = "order" sortable_field_name = "order"
show_change_link=True show_change_link=True
classes = ['collapse'] classes = ['collapse']
classes = ['collapse'] verbose_name_plural = "Geltungsbereich-Abschnitte"
#inline=inhalt fieldsets = (
(None, {
'fields': ('abschnitttyp', 'inhalt', 'order'),
'classes': ('wide',),
}),
)
class EinleitungInline(NestedTabularInline): class EinleitungInline(NestedStackedInline):
model = Einleitung model = Einleitung
extra = 0 extra = 0
sortable_field_name = "order" sortable_field_name = "order"
show_change_link = True show_change_link = True
classes = ['collapse'] classes = ['collapse']
verbose_name_plural = "Einleitungs-Abschnitte"
fieldsets = (
(None, {
'fields': ('abschnitttyp', 'inhalt', 'order'),
'classes': ('wide',),
}),
)
class VorgabeForm(forms.ModelForm): class VorgabeForm(forms.ModelForm):
referenzen = TreeNodeMultipleChoiceField(queryset=Referenz.objects.all(), required=False) referenzen = TreeNodeMultipleChoiceField(queryset=Referenz.objects.all(), required=False)
@@ -67,17 +97,31 @@ class VorgabeForm(forms.ModelForm):
model = Vorgabe model = Vorgabe
fields = '__all__' fields = '__all__'
class VorgabeInline(SortableInlineAdminMixin, NestedTabularInline): # or StackedInline for more vertical layout class VorgabeInline(SortableInlineAdminMixin, NestedStackedInline):
model = Vorgabe model = Vorgabe
form = VorgabeForm form = VorgabeForm
extra = 0 extra = 0
sortable_field_name = "order" # Add this - make sure your Vorgabe model has an 'order' field sortable_field_name = "order"
#show_change_link = True show_change_link = False
inlines = [VorgabeKurztextInline,VorgabeLangtextInline,ChecklistenfragenInline] can_delete = False
inlines = [VorgabeKurztextInline, VorgabeLangtextInline, ChecklistenfragenInline]
autocomplete_fields = ['stichworte','referenzen','relevanz'] autocomplete_fields = ['stichworte','referenzen','relevanz']
#search_fields=['nummer','name']ModelAdmin. # Remove collapse class so Vorgaben show by default
list_filter=['stichworte']
#classes=["collapse"] fieldsets = (
('Grunddaten', {
'fields': (('order', 'nummer'), ('thema', 'titel')),
'classes': ('wide',),
}),
('Gültigkeit', {
'fields': (('gueltigkeit_von', 'gueltigkeit_bis'),),
'classes': ('wide',),
}),
('Verknüpfungen', {
'fields': (('referenzen', 'stichworte', 'relevanz'),),
'classes': ('wide',),
}),
)
class StichworterklaerungInline(NestedTabularInline): class StichworterklaerungInline(NestedTabularInline):
model=Stichworterklaerung model=Stichworterklaerung
@@ -104,16 +148,31 @@ class PersonAdmin(admin.ModelAdmin):
@admin.register(Dokument) @admin.register(Dokument)
class DokumentAdmin(SortableAdminBase, NestedModelAdmin): class DokumentAdmin(SortableAdminBase, NestedModelAdmin):
actions_on_top=True actions_on_top=True
inlines = [EinleitungInline,GeltungsbereichInline,VorgabeInline] inlines = [EinleitungInline, GeltungsbereichInline, VorgabeInline]
#filter_horizontal=['autoren','pruefende'] filter_horizontal=['autoren','pruefende']
list_display=['nummer','name','dokumententyp'] list_display=['nummer','name','dokumententyp','gueltigkeit_von','gueltigkeit_bis','aktiv']
search_fields=['nummer','name'] search_fields=['nummer','name']
class Media: list_filter=['dokumententyp','aktiv','gueltigkeit_von']
# js = ('admin/js/vorgabe_collapse.js',)
css = { fieldsets = (
'all': ('admin/css/vorgabe_border.css', ('Grunddaten', {
# 'admin/css/vorgabe_collapse.css', 'fields': ('nummer', 'name', 'dokumententyp', 'aktiv'),
'classes': ('wide',),
}),
('Verantwortlichkeiten', {
'fields': ('autoren', 'pruefende'),
'classes': ('wide', 'collapse'),
}),
('Gültigkeit & Metadaten', {
'fields': ('gueltigkeit_von', 'gueltigkeit_bis', 'signatur_cso', 'anhaenge'),
'classes': ('wide', 'collapse'),
}),
) )
class Media:
js = ('admin/js/vorgabe_collapse.js',)
css = {
'all': ('admin/css/vorgabe_border.css',)
} }

View File

@@ -1,14 +0,0 @@
apiVersion: v2
name: vorgabenui
description: Helm chart for VorgabenUI Django application with Kroki diagram service
type: application
version: 0.1.0
appVersion: "0.939"
keywords:
- django
- kroki
- diagrams
- vorgabenui
maintainers:
- name: adebaumann
email: adebaumann@baumann.gr

View File

@@ -1,51 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "vorgabenui.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "vorgabenui.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "vorgabenui.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "vorgabenui.labels" -}}
helm.sh/chart: {{ include "vorgabenui.chart" . }}
{{ include "vorgabenui.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "vorgabenui.selectorLabels" -}}
app.kubernetes.io/name: {{ include "vorgabenui.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

View File

@@ -1,59 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "vorgabenui.fullname" . }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "vorgabenui.labels" . | nindent 4 }}
app.kubernetes.io/component: django
spec:
replicas: {{ .Values.django.replicaCount }}
selector:
matchLabels:
{{- include "vorgabenui.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: django
template:
metadata:
labels:
{{- include "vorgabenui.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: django
spec:
securityContext:
{{- toYaml .Values.django.securityContext | nindent 8 }}
initContainers:
- name: loader
image: "{{ .Values.django.dataLoader.image.repository }}:{{ .Values.django.dataLoader.image.tag }}"
command: [ "sh","-c","cp -n preload/preload.sqlite3 /data/db.sqlite3; chown -R 999:999 /data; ls -la /data; sleep 10; exit 0" ]
volumeMounts:
- name: data
mountPath: /data
containers:
- name: web
image: "{{ .Values.django.image.repository }}:{{ .Values.django.image.tag }}"
imagePullPolicy: {{ .Values.django.image.pullPolicy }}
ports:
- containerPort: {{ .Values.django.service.port }}
volumeMounts:
- name: data
mountPath: /app/data
readinessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 6
livenessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 20
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
volumes:
- name: data
persistentVolumeClaim:
claimName: {{ include "vorgabenui.fullname" . }}-data-pvc

View File

@@ -1,17 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "vorgabenui.fullname" . }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "vorgabenui.labels" . | nindent 4 }}
app.kubernetes.io/component: django
spec:
type: {{ .Values.django.service.type }}
selector:
{{- include "vorgabenui.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: django
ports:
- port: {{ .Values.django.service.port }}
targetPort: {{ .Values.django.service.port }}

View File

@@ -1,33 +0,0 @@
{{- if .Values.ingress.enabled }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "vorgabenui.fullname" . }}
namespace: {{ .Values.global.namespace }}
labels:
{{- include "vorgabenui.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: {{ .Values.ingress.path }}
pathType: {{ .Values.ingress.pathType }}
backend:
service:
name: {{ include "vorgabenui.fullname" . }}
port:
number: {{ .Values.django.service.port }}
{{- if .Values.ingress.tls }}
tls:
{{- toYaml .Values.ingress.tls | nindent 4 }}
{{- end }}
{{- end }}

View File

@@ -1,48 +0,0 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "vorgabenui.fullname" . }}-kroki
namespace: {{ .Values.global.namespace }}
labels:
{{- include "vorgabenui.labels" . | nindent 4 }}
app.kubernetes.io/component: kroki
spec:
replicas: {{ .Values.kroki.replicaCount }}
selector:
matchLabels:
{{- include "vorgabenui.selectorLabels" . | nindent 6 }}
app.kubernetes.io/component: kroki
template:
metadata:
labels:
{{- include "vorgabenui.selectorLabels" . | nindent 8 }}
app.kubernetes.io/component: kroki
spec:
containers:
- name: kroki
image: "{{ .Values.kroki.image.repository }}:{{ .Values.kroki.image.tag }}"
ports:
- containerPort: {{ .Values.kroki.service.port }}
readinessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 6
livenessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 20
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3
{{- range $service := .Values.kroki.services }}
- name: {{ $service }}
image: "{{ $service.image.repository }}:{{ $service.image.tag }}"
ports:
- containerPort: {{ $service.port }}
{{- end }}

View File

@@ -1,16 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: {{ include "vorgabenui.fullname" . }}-kroki
namespace: {{ .Values.global.namespace }}
labels:
{{- include "vorgabenui.labels" . | nindent 4 }}
app.kubernetes.io/component: kroki
spec:
selector:
{{- include "vorgabenui.selectorLabels" . | nindent 4 }}
app.kubernetes.io/component: kroki
ports:
- port: {{ .Values.kroki.service.port }}
targetPort: {{ .Values.kroki.service.port }}

View File

@@ -1,19 +0,0 @@
{{- if .Values.persistence.enabled }}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "vorgabenui.fullname" . }}-data-pvc
namespace: {{ .Values.global.namespace }}
labels:
{{- include "vorgabenui.labels" . | nindent 4 }}
spec:
accessModes:
{{- toYaml .Values.persistence.accessModes | nindent 4 }}
resources:
requests:
storage: {{ .Values.persistence.size }}
{{- if .Values.persistence.storageClass }}
storageClassName: {{ .Values.persistence.storageClass }}
{{- end }}
{{- end }}

View File

@@ -1,77 +0,0 @@
# Global settings
global:
namespace: vorgabenui
# Django application settings
django:
replicaCount: 1
image:
repository: git.baumann.gr/adebaumann/vui
tag: "0.939"
pullPolicy: Always
# Data loader init container
dataLoader:
image:
repository: git.baumann.gr/adebaumann/vui-data-loader
tag: "0.8"
# Security context
securityContext:
fsGroup: 999
fsGroupChangePolicy: "OnRootMismatch"
# Service settings
service:
type: ClusterIP
port: 8000
# Kroki diagram service settings
kroki:
replicaCount: 1
# Main kroki service
image:
repository: git.baumann.gr/adebaumann/kroki
tag: "0.026"
# Additional diagram services
services:
mermaid:
image:
repository: git.baumann.gr/adebaumann/kroki-mermaid
tag: "0.026"
port: 8002
bpmn:
image:
repository: git.baumann.gr/adebaumann/kroki-bpmn
tag: "0.026"
port: 8003
excalidraw:
image:
repository: git.baumann.gr/adebaumann/kroki-excalidraw
tag: "0.026"
port: 8004
# Service settings
service:
port: 8000
# Persistent storage
persistence:
enabled: true
storageClass: ""
accessModes:
- ReadWriteOnce
size: 2Gi
# Ingress settings
ingress:
enabled: true
className: ""
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
host: vorgabenportal.knowyoursecurity.com
path: /
pathType: Prefix
tls: []

View File

@@ -5,7 +5,6 @@
border-radius: 8px; border-radius: 8px;
padding: 15px; padding: 15px;
margin-bottom: 50px; margin-bottom: 50px;
background-color: #f9f9f9;
box-shadow: 0 2px 5px rgba(0,0,0,0.1); box-shadow: 0 2px 5px rgba(0,0,0,0.1);
} }

View File

@@ -1,21 +1,58 @@
window.addEventListener('load', function () { window.addEventListener('load', function () {
setTimeout(() => { setTimeout(() => {
const vorgabenBlocks = document.querySelectorAll('.djn-dynamic-form-Standards-vorgabe'); // Try different selectors for nested admin vorgabe elements
console.log("Found", vorgabenBlocks.length, "Vorgaben blocks"); const selectors = [
'.djn-dynamic-form-dokumente-vorgabe',
'.djn-dynamic-form-Standards-vorgabe',
'.inline-related[data-inline-type="stacked"]',
'.nested-inline'
];
let vorgabenBlocks = [];
for (const selector of selectors) {
vorgabenBlocks = document.querySelectorAll(selector);
if (vorgabenBlocks.length > 0) {
console.log("Found", vorgabenBlocks.length, "Vorgaben blocks with selector:", selector);
break;
}
}
if (vorgabenBlocks.length === 0) {
console.log("No Vorgaben blocks found, trying fallback...");
// Fallback: look for any inline with vorgabe in the class
vorgabenBlocks = document.querySelectorAll('[class*="vorgabe"]');
}
vorgabenBlocks.forEach((block, index) => { vorgabenBlocks.forEach((block, index) => {
const header = document.createElement('div'); // Find the existing title/header within the vorgabe block
header.className = 'vorgabe-toggle-header'; const existingHeader = block.querySelector('h3, .inline-label, .module h2, .djn-inline-header');
header.innerHTML = `▼ Vorgabe ${index + 1}`;
header.style.cursor = 'pointer';
block.parentNode.insertBefore(header, block); if (existingHeader) {
// Make the existing header clickable for collapse/expand
existingHeader.style.cursor = 'pointer';
existingHeader.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
header.addEventListener('click', () => { // Find all content to collapse - everything except the header itself
const isHidden = block.style.display === 'none'; const allChildren = Array.from(block.children);
block.style.display = isHidden ? '' : 'none'; const contentElements = allChildren.filter(child => child !== existingHeader && !child.contains(existingHeader));
header.innerHTML = `${isHidden ? '▼' : '▶'} Vorgabe ${index + 1}`;
contentElements.forEach(element => {
const isHidden = element.style.display === 'none';
element.style.display = isHidden ? '' : 'none';
}); });
// Update the header text to show collapse state
const originalText = existingHeader.textContent.replace(/[▼▶]\s*/, '');
const anyHidden = contentElements.some(el => el.style.display === 'none');
existingHeader.innerHTML = `${anyHidden ? '▶' : '▼'} ${originalText}`;
}); });
}, 500); // wait 500ms to allow nested inlines to render
// Add initial collapse indicator
const originalText = existingHeader.textContent;
existingHeader.innerHTML = `${originalText}`;
}
});
}, 1000); // wait longer to allow nested inlines to render
}); });