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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.260.007"
VERSION = "0.260.008"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
103 changes: 103 additions & 0 deletions application/single_app/static/js/admin/admin_card_links.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// admin_card_links.js
// Resolves links that point at an Admin Settings card rather than at a tab.
//
// Cross-tab links used to name a tab button directly, for example
// switchTab(event, 'workspaces-tab'). That couples the link to a tab id, so a
// tab rename or an information-architecture change silently breaks the link:
// the button no longer exists, no pane is activated, and the URL hash is left
// pointing at nothing.
//
// A link declares the card it wants instead:
//
// <a href="#redis-cache-section" data-admin-link="redis-cache-section">
//
// The owning tab is resolved from the DOM at click time, so card ids are the
// only contract. Cards can move between tabs, and tabs can be renamed or
// regrouped, without touching a single link.

const HIGHLIGHT_CLASS = 'admin-card-link-target';
const HIGHLIGHT_MS = 1600;

/**
* Find the tab pane that contains a card.
* @param {string} cardId Element id of the target card.
* @returns {{card: HTMLElement, paneId: string|null}|null}
*/
export function resolveAdminCard(cardId) {
const card = document.getElementById(cardId);
if (!card) {
return null;
}

const pane = card.closest('.tab-pane');
return { card, paneId: pane ? pane.id : null };
}

/**
* Activate the tab owning a card, scroll to it, and highlight it briefly.
* @param {string} cardId Element id of the target card.
* @returns {boolean} Whether the card was found.
*/
export function openAdminCard(cardId) {
const resolved = resolveAdminCard(cardId);
if (!resolved) {
console.warn(`openAdminCard: no card with id "${cardId}"`);
return false;
}

const { card, paneId } = resolved;

if (paneId && typeof window.showAdminTab === 'function') {
window.showAdminTab(paneId);
syncSidebarActiveState(paneId);
}

// Let the pane become visible before measuring scroll position.
window.setTimeout(() => {
card.scrollIntoView({ behavior: 'smooth', block: 'start' });
card.classList.add(HIGHLIGHT_CLASS);

Check warning on line 58 in application/single_app/static/js/admin/admin_card_links.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
window.setTimeout(() => card.classList.remove(HIGHLIGHT_CLASS), HIGHLIGHT_MS);

Check warning on line 59 in application/single_app/static/js/admin/admin_card_links.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
}, 120);

return true;
}

/**
* Mirror the activated tab in the sidebar, when the sidebar layout is in use.
* @param {string} paneId Tab pane id that was activated.
*/
function syncSidebarActiveState(paneId) {
const navLink = document.querySelector(`.admin-nav-tab[data-tab="${paneId}"]`);
if (!navLink) {
return;
}

document.querySelectorAll('.admin-nav-tab, .admin-nav-section').forEach((link) => {
link.classList.remove('active');

Check warning on line 76 in application/single_app/static/js/admin/admin_card_links.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
});
navLink.classList.add('active');

Check warning on line 78 in application/single_app/static/js/admin/admin_card_links.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
}

/**
* Delegate clicks so links added after load keep working.
*/
export function initAdminCardLinks() {
document.addEventListener('click', (event) => {
const link = event.target.closest('[data-admin-link]');
if (!link) {
return;
}

const cardId = link.getAttribute('data-admin-link');
if (!cardId) {
return;
}

event.preventDefault();
openAdminCard(cardId);
});
}

window.openAdminCard = openAdminCard;

document.addEventListener('DOMContentLoaded', initAdminCardLinks);
25 changes: 0 additions & 25 deletions application/single_app/static/js/admin/admin_settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -9227,31 +9227,6 @@ function clearStatusAlert(statusAlert) {
}


function switchTab(event, tabButtonId) {
event.preventDefault();
const triggerEl = document.getElementById(tabButtonId);
if (triggerEl) {
const tabObj = new bootstrap.Tab(triggerEl);
tabObj.show();
return;
}

const inferredTabId = tabButtonId.replace(/-tab$/, '');
if (typeof window.showAdminTab === 'function') {
window.showAdminTab(inferredTabId);

const navLink = document.querySelector(`.admin-nav-tab[data-tab="${inferredTabId}"]`);
if (navLink) {
document.querySelectorAll('.admin-nav-tab, .admin-nav-section').forEach(link => {
link.classList.remove('active');
});
navLink.classList.add('active');
}
}
}

window.switchTab = switchTab;

function togglePassword(btnId, inputId) {
const btn = document.getElementById(btnId);
const inp = document.getElementById(inputId);
Expand Down
220 changes: 220 additions & 0 deletions application/single_app/static/js/admin/admin_settings_dependencies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// admin_settings_dependencies.js
// Announces and enforces Admin Settings options that require another option.
//
// Some settings only work when a different setting is enabled, and those two
// settings often live in different tabs. Previously that was communicated in
// prose, or in a tooltip, or only by a flash message after saving, so an admin

Check warning on line 6 in application/single_app/static/js/admin/admin_settings_dependencies.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
// could turn something on and have nothing happen with no visible reason.
//
// A dependent card declares what it needs:
//
// <div class="card" id="file-sync-section"
// data-requires="enable_redis_cache"
// data-requires-label="Redis Cache"
// data-requires-target="redis-cache-section"
// data-requires-mode="warn">
//
// When the prerequisite is off, a notice is inserted at the top of the card
// containing a mirror of the prerequisite control and a link to its card, so
// the admin can satisfy it inline or jump to the full configuration.
//
// Modes:
// block (default) disables the dependent inputs until the prerequisite is on

Check warning on line 22 in application/single_app/static/js/admin/admin_settings_dependencies.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
// warn leaves inputs usable, for prerequisites the backend already
// accepts as intent and reconciles later
//
// This is a usability layer only. The backend remains authoritative and still
// refuses or flashes on unmet prerequisites.

const NOTICE_CLASS = 'admin-dependency-notice';
const DISABLED_FLAG = 'data-dependency-disabled';

Check warning on line 30 in application/single_app/static/js/admin/admin_settings_dependencies.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.

/**
* Read every dependency declared in the document.
* @returns {Array<object>} Parsed dependency descriptors.
*/
function collectDependencies() {
return Array.from(document.querySelectorAll('[data-requires]')).map((card) => ({
card,
prerequisiteId: card.getAttribute('data-requires'),
label: card.getAttribute('data-requires-label') || 'another setting',
targetCardId: card.getAttribute('data-requires-target') || '',
mode: card.getAttribute('data-requires-mode') === 'warn' ? 'warn' : 'block',
description: card.getAttribute('data-requires-description') || '',
// Optional selector limiting which controls the dependency guards, for
// cards that hold a mix of dependent and independent settings.
scope: card.getAttribute('data-requires-scope') || '',
}));
}

/**
* Whether a prerequisite control is currently satisfied.
* @param {HTMLElement|null} control Prerequisite input.
* @returns {boolean}
*/
function isSatisfied(control) {
if (!control) {
return true;
}
if (control.type === 'checkbox' || control.type === 'radio') {
return control.checked;
}
return Boolean(control.value);
}

/**
* Build the notice shown when a prerequisite is unmet.
* @param {object} dependency Dependency descriptor.
* @param {HTMLElement} prerequisite Prerequisite control.
* @returns {HTMLElement}
*/
function buildNotice(dependency, prerequisite) {
const notice = document.createElement('div');
notice.className = `alert ${dependency.mode === 'warn' ? 'alert-warning' : 'alert-info'} ${NOTICE_CLASS}`;
notice.setAttribute('role', 'note');

Check warning on line 74 in application/single_app/static/js/admin/admin_settings_dependencies.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.

const heading = document.createElement('div');
heading.className = 'fw-semibold mb-1';
const icon = document.createElement('i');
icon.className = 'bi bi-info-circle me-2';
icon.setAttribute('aria-hidden', 'true');

Check warning on line 80 in application/single_app/static/js/admin/admin_settings_dependencies.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Moderate - Changed line contains obfuscation, dynamic loading, or hidden payload marker. Recommendation%3A Confirm the changed code is not hiding behavior, decoding payloads, or bypassing normal review.
heading.append(icon, document.createTextNode(`This needs ${dependency.label}`));
notice.appendChild(heading);

const body = document.createElement('p');
body.className = 'mb-2';
body.textContent = dependency.description
|| (dependency.mode === 'warn'
? `These settings are saved, but stay inactive until ${dependency.label} is enabled and configured.`
: `These settings are unavailable until ${dependency.label} is enabled.`);
notice.appendChild(body);

const actions = document.createElement('div');
actions.className = 'd-flex flex-wrap align-items-center gap-3';

// Inline mirror: satisfy the prerequisite without leaving this tab. It
// carries no name attribute, so only the canonical input is submitted.
if (prerequisite && prerequisite.type === 'checkbox') {
const wrapper = document.createElement('div');
wrapper.className = 'form-check form-switch mb-0';

const proxy = document.createElement('input');
proxy.type = 'checkbox';
proxy.className = 'form-check-input';
proxy.id = `${dependency.card.id}-requires-proxy`;
proxy.checked = prerequisite.checked;
proxy.setAttribute('data-dependency-proxy-for', prerequisite.id);
proxy.setAttribute('data-ignore-settings-change', 'true');

proxy.addEventListener('change', () => {
prerequisite.checked = proxy.checked;
prerequisite.dispatchEvent(new Event('change', { bubbles: true }));
});

const proxyLabel = document.createElement('label');
proxyLabel.className = 'form-check-label ms-2';
proxyLabel.setAttribute('for', proxy.id);
proxyLabel.textContent = `Enable ${dependency.label}`;

wrapper.append(proxy, proxyLabel);
actions.appendChild(wrapper);
}

if (dependency.targetCardId) {
const link = document.createElement('a');
link.href = `#${dependency.targetCardId}`;
link.setAttribute('data-admin-link', dependency.targetCardId);
link.className = 'small';
link.textContent = `Go to ${dependency.label}`;
actions.appendChild(link);
}

notice.appendChild(actions);
return notice;
}

/**
* Enable or disable the controls a dependency guards.

Check warning on line 137 in application/single_app/static/js/admin/admin_settings_dependencies.js

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
* @param {object} dependency Dependency descriptor.
* @param {boolean} satisfied Whether the prerequisite is met.
*/
function setControlsDisabled(dependency, satisfied) {
if (dependency.mode === 'warn') {
return;
}

const selector = dependency.scope || 'input, select, textarea, button';
dependency.card.querySelectorAll(selector).forEach((control) => {
if (control.closest(`.${NOTICE_CLASS}`)) {
return;
}

if (!satisfied) {
// Remember controls that were already disabled for other
// reasons so re-enabling does not override them.
if (!control.disabled) {
control.setAttribute(DISABLED_FLAG, 'true');
control.disabled = true;
}
} else if (control.getAttribute(DISABLED_FLAG) === 'true') {
control.removeAttribute(DISABLED_FLAG);
control.disabled = false;
}
});
}

/**
* Apply the current state of one dependency.
* @param {object} dependency Dependency descriptor.
*/
function applyDependency(dependency) {
const prerequisite = document.getElementById(dependency.prerequisiteId);
const satisfied = isSatisfied(prerequisite);

const existing = dependency.card.querySelector(`:scope > .${NOTICE_CLASS}`);
if (existing) {
existing.remove();
}

setControlsDisabled(dependency, satisfied);

if (satisfied) {
return;
}

const notice = buildNotice(dependency, prerequisite);
const heading = dependency.card.querySelector('h4, h5, h6');
if (heading && heading.parentElement === dependency.card) {
heading.insertAdjacentElement('afterend', notice);
} else {
dependency.card.prepend(notice);
}
}

/**
* Wire up every declared dependency and keep it live.
*/
export function initAdminSettingsDependencies() {
const dependencies = collectDependencies();
if (!dependencies.length) {
return;
}

dependencies.forEach((dependency) => {
applyDependency(dependency);

const prerequisite = document.getElementById(dependency.prerequisiteId);
if (!prerequisite) {
console.warn(
`admin dependencies: "${dependency.card.id}" requires missing control `
+ `"${dependency.prerequisiteId}"`,
);
return;
}

prerequisite.addEventListener('change', () => applyDependency(dependency));
prerequisite.addEventListener('input', () => applyDependency(dependency));
});
}

document.addEventListener('DOMContentLoaded', initAdminSettingsDependencies);
8 changes: 4 additions & 4 deletions application/single_app/templates/admin/_panes/citation.html
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@ <h6><strong>Video Files</strong></h6>
<p class="mt-2 mb-0">
<small class="text-muted">
Reference:
<a href="#workspaces" onclick="switchTab(event, 'workspaces-tab')">
<a href="#video-intelligence-section" data-admin-link="video-intelligence-section">
Enable Video File Support
</a>
(see the Workspaces tab)
(see AI Video Intelligence under Search and Extract)
</small>
</p>
{% endif %}
Expand Down Expand Up @@ -231,10 +231,10 @@ <h6><strong>Audio Files</strong></h6>
<p class="mt-2 mb-0">
<small class="text-muted">
Reference:
<a href="#workspaces" onclick="switchTab(event, 'workspaces-tab')">
<a href="#ai-voice-chat-section" data-admin-link="ai-voice-chat-section">
Enable Audio File Support
</a>
(see the Workspaces tab)
(see AI Voice Conversations under Search and Extract)
</small>
</p>
{% endif %}
Expand Down
Loading
Loading