Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 135 additions & 2 deletions application/single_app/static/js/public/public_workspace.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// static/js/public_workspace.js
import { showToast } from "./chat/chat-toast.js";
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import path for showToast is incorrect. The file public_workspace.js is located in /static/js/public/, so the relative path ./chat/chat-toast.js would try to load from /static/js/public/chat/ which doesn't exist. The correct relative path should be ../chat/chat-toast.js to go up one directory level to /static/js/ and then into the chat/ subdirectory.

Suggested change
import { showToast } from "./chat/chat-toast.js";
import { showToast } from "../chat/chat-toast.js";

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script public_workspace.js uses ES6 module syntax (import statement) but is loaded as a regular script without type="module" attribute in the HTML. This will cause a syntax error. Either: 1) Add type="module" to the script tag in the template (line 749 of public_workspaces.html), or 2) Remove the import and use the pattern already established in the codebase where showToast is accessed globally (check how group_workspaces.html handles this inline).

Suggested change
import { showToast } from "./chat/chat-toast.js";

Copilot uses AI. Check for mistakes.

'use strict';

// --- Global State ---
Expand Down Expand Up @@ -267,6 +269,13 @@ function updatePublicRoleDisplay(){
if (display) display.style.display = 'block';
if (uploadSection) uploadSection.style.display = ['Owner','Admin','DocumentManager'].includes(userRoleInActivePublic) ? 'block' : 'none';
// uploadHr was removed from template, so skip

// Control visibility of Settings tab (only for Owners and Admins)
const settingsTabNav = document.getElementById('public-settings-tab-nav');
const canManageSettings = ['Owner', 'Admin'].includes(userRoleInActivePublic);
if (settingsTabNav) {
settingsTabNav.classList.toggle('d-none', !canManageSettings);
}
} else {
if (display) display.style.display = 'none';
}
Expand Down Expand Up @@ -318,11 +327,135 @@ function updateWorkspaceUIBasedOnStatus(status) {
}
}

// ===================== PUBLIC RETENTION POLICY =====================

async function loadPublicRetentionSettings() {
if (!activePublicId) return;

const convSelect = document.getElementById('public-conversation-retention-days');
const docSelect = document.getElementById('public-document-retention-days');

if (!convSelect || !docSelect) return; // Settings tab not available

console.log('Loading public workspace retention settings for:', activePublicId);

try {
// Fetch organization defaults for public workspace retention
const orgDefaultsResp = await fetch('/api/retention-policy/defaults/public');
const orgData = await orgDefaultsResp.json();

if (orgData.success) {
const convDefaultOption = convSelect.querySelector('option[value="default"]');
const docDefaultOption = docSelect.querySelector('option[value="default"]');

if (convDefaultOption) {
convDefaultOption.textContent = `Using organization default (${orgData.default_conversation_label})`;
}
if (docDefaultOption) {
docDefaultOption.textContent = `Using organization default (${orgData.default_document_label})`;
}
console.log('Loaded org defaults:', orgData);
}
} catch (error) {
console.error('Error loading public workspace retention defaults:', error);
}

// Load current public workspace's retention policy settings
try {
const workspaceResp = await fetch(`/api/public_workspaces/${activePublicId}`);

if (!workspaceResp.ok) {
throw new Error(`Failed to fetch workspace: ${workspaceResp.status}`);
}

const workspaceData = await workspaceResp.json();
console.log('Loaded workspace data:', workspaceData);

// API returns workspace object directly (not wrapped in success/workspace)
if (workspaceData && workspaceData.retention_policy) {
const retentionPolicy = workspaceData.retention_policy;
let convRetention = retentionPolicy.conversation_retention_days;
let docRetention = retentionPolicy.document_retention_days;

console.log('Found retention policy:', retentionPolicy);

// If undefined, use 'default'
if (convRetention === undefined || convRetention === null) convRetention = 'default';
if (docRetention === undefined || docRetention === null) docRetention = 'default';

convSelect.value = convRetention;
docSelect.value = docRetention;
console.log('Set retention values to:', { conv: convRetention, doc: docRetention });
} else {
// Set to organization default if no retention policy set
console.log('No retention policy found, using defaults');
convSelect.value = 'default';
docSelect.value = 'default';
}
} catch (error) {
console.error('Error loading public workspace retention settings:', error);
// Set defaults on error
convSelect.value = 'default';
docSelect.value = 'default';
}
}

async function savePublicRetentionSettings() {
if (!activePublicId) {
showToast('No active public workspace selected.', 'warning');
return;
}

const convSelect = document.getElementById('public-conversation-retention-days');
const docSelect = document.getElementById('public-document-retention-days');
const statusSpan = document.getElementById('public-retention-save-status');

if (!convSelect || !docSelect) return;

const retentionData = {
conversation_retention_days: convSelect.value,
document_retention_days: docSelect.value
};
Comment on lines +415 to +418
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This issue was flagged in previous review (Comment 7) but has not been fixed. The select options include value "default", but this value is sent directly to the API endpoint which only accepts integers or 'none'. When a user tries to save while "default" is selected, the backend will reject it with a 400 error. Either map "default" to a backend-supported representation (e.g., omit the field or use null), or update the backend to handle "default" appropriately.

Suggested change
const retentionData = {
conversation_retention_days: convSelect.value,
document_retention_days: docSelect.value
};
const retentionData = {};
if (convSelect.value !== 'default') {
retentionData.conversation_retention_days = convSelect.value;
}
if (docSelect.value !== 'default') {
retentionData.document_retention_days = docSelect.value;
}

Copilot uses AI. Check for mistakes.

console.log('Saving public workspace retention settings:', retentionData);

// Show saving status
if (statusSpan) {
statusSpan.innerHTML = '<span class="text-info"><i class="bi bi-hourglass-split"></i> Saving...</span>';
}

try {
const response = await fetch(`/api/retention-policy/public/${activePublicId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(retentionData)
});

const data = await response.json();
console.log('Save response:', data);

if (response.ok && data.success) {
if (statusSpan) {
statusSpan.innerHTML = '<span class="text-success"><i class="bi bi-check-circle-fill"></i> Saved successfully!</span>';
setTimeout(() => { statusSpan.innerHTML = ''; }, 3000);
}
console.log('Public workspace retention settings saved successfully');
} else {
throw new Error(data.error || 'Failed to save retention settings');
}
} catch (error) {
console.error('Error saving public workspace retention settings:', error);
if (statusSpan) {
statusSpan.innerHTML = `<span class="text-danger"><i class="bi bi-exclamation-circle-fill"></i> Error: ${error.message}</span>`;
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This XSS vulnerability was flagged in previous review (Comment 3) but has not been fixed. Setting innerHTML with unsanitized error.message allows potential DOM-based XSS if the error message contains HTML markup. The error message could originate from server responses or thrown exceptions. Use textContent for the error message portion or construct the element safely using DOM methods instead of template literals in innerHTML.

Suggested change
statusSpan.innerHTML = `<span class="text-danger"><i class="bi bi-exclamation-circle-fill"></i> Error: ${error.message}</span>`;
// Build the error status content safely without injecting HTML from error.message
statusSpan.innerHTML = '';
const errorSpan = document.createElement('span');
errorSpan.className = 'text-danger';
const icon = document.createElement('i');
icon.className = 'bi bi-exclamation-circle-fill';
errorSpan.appendChild(icon);
// Add a space and "Error: " label
errorSpan.appendChild(document.createTextNode(' Error: '));
// Append the error message as text to avoid XSS
const messageText = error && typeof error.message === 'string' ? error.message : 'Unknown error';
errorSpan.appendChild(document.createTextNode(messageText));
statusSpan.appendChild(errorSpan);

Copilot uses AI. Check for mistakes.
}
showToast(`Error saving retention settings: ${error.message}`, 'danger');
}
}

Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function savePublicRetentionSettings is called via inline onclick handler in the template (line 439 of public_workspaces.html), but this function is not exported to the window object. In ES6 modules, functions are not automatically global. You need to add window.savePublicRetentionSettings = savePublicRetentionSettings; and window.loadPublicRetentionSettings = loadPublicRetentionSettings; after the function definitions to make them accessible from inline onclick handlers. See how group_agents.js handles this pattern with window.fetchGroupAgents = fetchGroupAgents; (line 398).

Suggested change
// Expose retention settings functions for inline onclick handlers in templates
window.loadPublicRetentionSettings = loadPublicRetentionSettings;
window.savePublicRetentionSettings = savePublicRetentionSettings;

Copilot uses AI. Check for mistakes.
function loadActivePublicData(){
const activeTab = document.querySelector('#publicWorkspaceTab .nav-link.active').dataset.bsTarget;
if(activeTab==='#public-docs-tab') fetchPublicDocs(); else fetchPublicPrompts();
updatePublicRoleDisplay(); updatePublicPromptsRoleUI(); updateWorkspaceStatusAlert();
loadPublicWorkspaceTags();
updatePublicRoleDisplay(); updatePublicPromptsRoleUI(); updateWorkspaceStatusAlert(); loadPublicRetentionSettings();
}

async function fetchPublicDocs(){
Expand Down
Loading
Loading