From 13ec05da83e6ef2404b7fca1266854b4c630e288 Mon Sep 17 00:00:00 2001 From: Bernd Rodler Date: Fri, 7 Aug 2026 13:38:12 +0200 Subject: [PATCH] feat: P2.9 VNCtalk + P2.10 Collabora + P2.11 Calendar Enhancements + P2.13 VNCdirectory Admin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - P2.9: VNCtalk video meeting — create/update meeting from event modal, 'Join Meeting' link in event detail. Admin config vnctalkServerUrl. - P2.10: Collabora online editing — 'Edit with Collabora' for office files, WOPI discovery + edit URL. Admin config collaboraServerUrl. - P2.11: Calendar enhancements — clickable links in descriptions, participant contact popover, Reply/Reply All from event, timezone picker, map links for locations. - P2.13: VNCdirectory IDP admin panel — Connection, SAML/IDP, LDAP, Authentication, Federated Apps configuration. Secret masking on display. --- app/(main)/admin/_tabs/vncdirectory.tsx | 620 +++++++++++++++++++ app/(main)/admin/layout.tsx | 2 + app/(main)/admin/page.tsx | 2 + app/(main)/admin/vncdirectory/page.tsx | 5 + app/api/admin/vncdirectory/route.ts | 146 +++++ app/api/auth/sso/start/route.ts | 11 + app/api/collabora/edit/route.ts | 32 + app/api/vnctalk/meeting/route.ts | 49 ++ components/calendar/event-detail-popover.tsx | 106 +++- components/calendar/event-modal.tsx | 242 +++++++- components/files/file-browser.tsx | 63 +- lib/admin/types.ts | 7 +- lib/admin/vncdirectory-config.ts | 117 ++++ lib/collabora/client.ts | 87 +++ lib/vnctalk/client.ts | 56 ++ stores/admin-tab-store.ts | 1 + 16 files changed, 1512 insertions(+), 34 deletions(-) create mode 100644 app/(main)/admin/_tabs/vncdirectory.tsx create mode 100644 app/(main)/admin/vncdirectory/page.tsx create mode 100644 app/api/admin/vncdirectory/route.ts create mode 100644 app/api/collabora/edit/route.ts create mode 100644 app/api/vnctalk/meeting/route.ts create mode 100644 lib/admin/vncdirectory-config.ts create mode 100644 lib/collabora/client.ts create mode 100644 lib/vnctalk/client.ts diff --git a/app/(main)/admin/_tabs/vncdirectory.tsx b/app/(main)/admin/_tabs/vncdirectory.tsx new file mode 100644 index 00000000..911c167d --- /dev/null +++ b/app/(main)/admin/_tabs/vncdirectory.tsx @@ -0,0 +1,620 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { Save, Loader2, Plus, X } from 'lucide-react'; +import { apiFetch } from '@/lib/browser-navigation'; + +interface VncDirectoryFormData { + enabled: boolean; + apiUrl: string; + apiKey: string; + samlEnabled: boolean; + samlIdpUrl: string; + samlSpCert: string; + samlIssuer: string; + ldapEnabled: boolean; + ldapUri: string; + ldapBindDn: string; + ldapBindPassword: string; + ldapSearchBase: string; + ldapType: 'openldap' | 'ms-ad'; + tfaEnabled: boolean; + oidcEnabled: boolean; + oidcClientId: string; + oidcDiscoveryUrl: string; + sessionTtl: number; + federatedApps: Record; +} + +const BLANK_FORM: VncDirectoryFormData = { + enabled: false, + apiUrl: '', + apiKey: '', + samlEnabled: false, + samlIdpUrl: '', + samlSpCert: '', + samlIssuer: '', + ldapEnabled: false, + ldapUri: '', + ldapBindDn: '', + ldapBindPassword: '', + ldapSearchBase: '', + ldapType: 'openldap', + tfaEnabled: false, + oidcEnabled: false, + oidcClientId: '', + oidcDiscoveryUrl: '', + sessionTtl: 28800, + federatedApps: {}, +}; + +export function VncDirectoryTab() { + const [config, setConfig] = useState({ ...BLANK_FORM }); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + const [dirty, setDirty] = useState(false); + + useEffect(() => { fetchConfig(); }, []); + + async function fetchConfig() { + setLoading(true); + try { + const res = await apiFetch('/api/admin/vncdirectory'); + if (res.ok) { + const data = await res.json(); + setConfig(data); + } + } finally { + setLoading(false); + } + } + + function updateField(key: K, value: VncDirectoryFormData[K]) { + setConfig((prev) => ({ ...prev, [key]: value })); + setDirty(true); + setMessage(null); + } + + function toggleBool(key: keyof VncDirectoryFormData) { + setConfig((prev) => ({ ...prev, [key]: !prev[key] })); + setDirty(true); + setMessage(null); + } + + function setFederatedApp(name: string, url: string) { + setConfig((prev) => ({ + ...prev, + federatedApps: { ...prev.federatedApps, [name]: url }, + })); + setDirty(true); + setMessage(null); + } + + function removeFederatedApp(name: string) { + setConfig((prev) => { + const next = { ...prev.federatedApps }; + delete next[name]; + return { ...prev, federatedApps: next }; + }); + setDirty(true); + setMessage(null); + } + + async function handleSave() { + setSaving(true); + setMessage(null); + + const res = await apiFetch('/api/admin/vncdirectory', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(config), + }); + + if (res.ok) { + setMessage({ type: 'success', text: 'VNCdirectory configuration saved.' }); + setDirty(false); + await fetchConfig(); + } else { + const data = await res.json(); + setMessage({ type: 'error', text: data.error || 'Failed to save' }); + } + setSaving(false); + } + + if (loading) { + return ( +
+ Loading... +
+ ); + } + + const federatedAppsList = Object.entries(config.federatedApps); + + return ( +
+
+
+

VNCdirectory

+

+ Centralized identity and directory integration (SAML, LDAP, 2FA) +

+
+ {dirty && ( + + )} +
+ + {message && ( +
+ {message.text} +
+ )} + +
+
+
+ Enabled +

+ Turn on VNCdirectory integration for identity management, SSO, and directory services +

+
+ +
+
+ + {config.enabled && ( + <> +
+
+ updateField('apiUrl', v)} + placeholder="https://vncdirectory.example.com" + /> + updateField('apiKey', v)} + placeholder="Enter API key" + /> +
+
+ +
+
+ toggleBool('samlEnabled')} + /> + {config.samlEnabled && ( + <> + updateField('samlIdpUrl', v)} + placeholder="https://idp.example.com/saml2/idp" + /> + updateField('samlIssuer', v)} + placeholder="urn:example:vncmail" + /> +
+ +