feat: P2.9 VNCtalk + P2.10 Collabora + P2.11 Calendar Enhancements + P2.13 VNCdirectory Admin
- 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.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { configManager } from "@/lib/admin/config-manager";
|
||||
|
||||
export async function getCollaboraEditUrl(
|
||||
fileId: string,
|
||||
fileName: string
|
||||
): Promise<string> {
|
||||
const serverUrl =
|
||||
configManager.get<string>("collaboraServerUrl") ||
|
||||
process.env.COLLABORA_SERVER_URL ||
|
||||
"";
|
||||
|
||||
if (!serverUrl) {
|
||||
throw new Error("COLLABORA_SERVER_URL is not configured");
|
||||
}
|
||||
|
||||
const base = serverUrl.replace(/\/+$/, "");
|
||||
const fileExt = fileName.split(".").pop()?.toLowerCase() || "";
|
||||
|
||||
// Collabora WOPI host discovery endpoint
|
||||
const response = await fetch(`${base}/hosting/discovery`, {
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Collabora discovery failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const discovery = await response.json();
|
||||
|
||||
// Find the WOPI action URL for the file extension
|
||||
let actionUrl: string | null = null;
|
||||
const mimeMap: Record<string, string> = {
|
||||
docx: "text",
|
||||
doc: "text",
|
||||
odt: "text",
|
||||
xlsx: "spreadsheet",
|
||||
xls: "spreadsheet",
|
||||
ods: "spreadsheet",
|
||||
pptx: "presentation",
|
||||
ppt: "presentation",
|
||||
odp: "presentation",
|
||||
};
|
||||
const docType = mimeMap[fileExt] || "text";
|
||||
|
||||
if (discovery.net?.zone) {
|
||||
const zones = Array.isArray(discovery.net.zone)
|
||||
? discovery.net.zone
|
||||
: [discovery.net.zone];
|
||||
for (const zone of zones) {
|
||||
const apps = Array.isArray(zone.app) ? zone.app : zone.app ? [zone.app] : [];
|
||||
for (const app of apps) {
|
||||
if (
|
||||
app.name &&
|
||||
docType &&
|
||||
app.name.toLowerCase().includes(docType.toLowerCase())
|
||||
) {
|
||||
const actions = Array.isArray(app.action)
|
||||
? app.action
|
||||
: app.action
|
||||
? [app.action]
|
||||
: [];
|
||||
for (const action of actions) {
|
||||
if (action.name === "edit" && action.urlsrc) {
|
||||
actionUrl = action.urlsrc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actionUrl) break;
|
||||
}
|
||||
if (actionUrl) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!actionUrl) {
|
||||
// Fallback: construct URL manually
|
||||
actionUrl = `${base}/loleaflet/dist/loleaflet.html`;
|
||||
}
|
||||
|
||||
// For now, return the base edit URL. A full WOPI implementation would
|
||||
// generate a WOPI src URL with an access token pointing back to this server.
|
||||
const wopiSrcUrl = `${actionUrl}?WOPISrc=${encodeURIComponent(
|
||||
`${process.env.NEXT_PUBLIC_APP_URL || `http://localhost:${process.env.PORT || 3000}`}/api/collabora/wopi/files/${encodeURIComponent(fileId)}`
|
||||
)}`;
|
||||
|
||||
return wopiSrcUrl;
|
||||
}
|
||||
Reference in New Issue
Block a user