feat(js): add app-http contracts and migrate helpdesk runtime layer
This commit is contained in:
@@ -2,133 +2,143 @@
|
||||
* Helpdesk settings page — auth mode toggle and connection test.
|
||||
*/
|
||||
import { showAsyncFlash } from '/js/components/app-async-flash.js';
|
||||
import { postForm } from '/js/core/app-http.js';
|
||||
import { resolveHost } from '/js/core/app-dom.js';
|
||||
|
||||
const settingsForm = document.getElementById('helpdesk-settings-form');
|
||||
const EMPTY_API = Object.freeze({ destroy: () => {} });
|
||||
|
||||
const getMessage = (key, fallback) => {
|
||||
if (!settingsForm || !settingsForm.dataset) {
|
||||
const resolveContainer = (root) => {
|
||||
const host = resolveHost(root);
|
||||
if (host instanceof HTMLElement && host.matches('.app-details-container[data-app-component="helpdesk-settings"]')) {
|
||||
return host;
|
||||
}
|
||||
return host.querySelector('.app-details-container[data-app-component="helpdesk-settings"]');
|
||||
};
|
||||
|
||||
const readMessage = (form, key, fallback) => {
|
||||
if (!form || !form.dataset) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return settingsForm.dataset[key] || fallback;
|
||||
return form.dataset[key] || fallback;
|
||||
};
|
||||
|
||||
// Toggle OAuth2 vs Basic Auth field visibility
|
||||
const authModeSelect = document.getElementById('auth_mode');
|
||||
const basicFields = document.getElementById('helpdesk-basic-auth-fields');
|
||||
const oauth2Fields = document.getElementById('helpdesk-oauth2-fields');
|
||||
|
||||
const toggleAuthFields = () => {
|
||||
if (!authModeSelect || !basicFields || !oauth2Fields) {
|
||||
return;
|
||||
export function initHelpdeskSettings(root = document) {
|
||||
const container = resolveContainer(root);
|
||||
if (!container) {
|
||||
return EMPTY_API;
|
||||
}
|
||||
|
||||
const isOAuth2 = authModeSelect.value === 'oauth2';
|
||||
basicFields.toggleAttribute('hidden', isOAuth2);
|
||||
oauth2Fields.toggleAttribute('hidden', !isOAuth2);
|
||||
};
|
||||
const settingsForm = container.querySelector('#helpdesk-settings-form');
|
||||
if (!settingsForm) {
|
||||
return EMPTY_API;
|
||||
}
|
||||
|
||||
if (authModeSelect) {
|
||||
authModeSelect.addEventListener('change', toggleAuthFields);
|
||||
const listenerController = new AbortController();
|
||||
const requestController = new AbortController();
|
||||
const on = (target, type, handler, options = {}) => {
|
||||
if (!target || typeof target.addEventListener !== 'function') {
|
||||
return;
|
||||
}
|
||||
target.addEventListener(type, handler, { ...options, signal: listenerController.signal });
|
||||
};
|
||||
|
||||
const authModeSelect = settingsForm.querySelector('#auth_mode');
|
||||
const basicFields = settingsForm.querySelector('#helpdesk-basic-auth-fields');
|
||||
const oauth2Fields = settingsForm.querySelector('#helpdesk-oauth2-fields');
|
||||
|
||||
const testButton = settingsForm.querySelector('#helpdesk-test-connection-button');
|
||||
const testResult = settingsForm.querySelector('#helpdesk-connection-test-result');
|
||||
|
||||
const overrideToggle = settingsForm.querySelector('#helpdesk-override-toggle');
|
||||
const tenantFields = settingsForm.querySelector('#helpdesk-tenant-fields');
|
||||
const saveTargetInput = settingsForm.querySelector('#helpdesk-settings-save-target');
|
||||
|
||||
const tenantAuthModeSelect = settingsForm.querySelector('#tenant_auth_mode');
|
||||
const tenantBasicFields = settingsForm.querySelector('#helpdesk-tenant-basic-auth-fields');
|
||||
const tenantOauth2Fields = settingsForm.querySelector('#helpdesk-tenant-oauth2-fields');
|
||||
|
||||
const setTestResult = (variant, message) => {
|
||||
if (!testResult) {
|
||||
return;
|
||||
}
|
||||
testResult.textContent = message;
|
||||
testResult.dataset.variant = variant;
|
||||
testResult.hidden = !message;
|
||||
};
|
||||
|
||||
const setButtonLoadingState = (isLoading) => {
|
||||
if (!testButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultLabel = testButton.dataset.labelDefault || readMessage(settingsForm, 'testDefaultLabel', 'Test connection');
|
||||
const loadingLabel = testButton.dataset.labelLoading || readMessage(settingsForm, 'testLoadingLabel', 'Testing...');
|
||||
|
||||
testButton.disabled = isLoading;
|
||||
testButton.toggleAttribute('aria-busy', isLoading);
|
||||
testButton.textContent = isLoading ? loadingLabel : defaultLabel;
|
||||
};
|
||||
|
||||
const toggleAuthFields = () => {
|
||||
if (!authModeSelect || !basicFields || !oauth2Fields) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isOAuth2 = authModeSelect.value === 'oauth2';
|
||||
basicFields.toggleAttribute('hidden', isOAuth2);
|
||||
oauth2Fields.toggleAttribute('hidden', !isOAuth2);
|
||||
};
|
||||
|
||||
on(authModeSelect, 'change', toggleAuthFields);
|
||||
toggleAuthFields();
|
||||
}
|
||||
|
||||
// Connection test button
|
||||
const testButton = document.getElementById('helpdesk-test-connection-button');
|
||||
const testResult = document.getElementById('helpdesk-connection-test-result');
|
||||
|
||||
const setTestResult = (variant, message) => {
|
||||
if (!testResult) {
|
||||
return;
|
||||
}
|
||||
|
||||
testResult.textContent = message;
|
||||
testResult.dataset.variant = variant;
|
||||
testResult.hidden = !message;
|
||||
};
|
||||
|
||||
const setButtonLoadingState = (isLoading) => {
|
||||
if (!testButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultLabel = testButton.dataset.labelDefault || getMessage('testDefaultLabel', 'Test connection');
|
||||
const loadingLabel = testButton.dataset.labelLoading || getMessage('testLoadingLabel', 'Testing...');
|
||||
|
||||
testButton.disabled = isLoading;
|
||||
testButton.toggleAttribute('aria-busy', isLoading);
|
||||
testButton.textContent = isLoading ? loadingLabel : defaultLabel;
|
||||
};
|
||||
|
||||
const parseJsonSafely = async (response) => {
|
||||
try {
|
||||
return await response.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Tenant override toggle — show/hide tenant fields + set save_target
|
||||
const overrideToggle = document.getElementById('helpdesk-override-toggle');
|
||||
const tenantFields = document.getElementById('helpdesk-tenant-fields');
|
||||
const saveTargetInput = document.getElementById('helpdesk-settings-save-target');
|
||||
|
||||
const tenantAuthModeSelect = document.getElementById('tenant_auth_mode');
|
||||
const tenantBasicFields = document.getElementById('helpdesk-tenant-basic-auth-fields');
|
||||
const tenantOauth2Fields = document.getElementById('helpdesk-tenant-oauth2-fields');
|
||||
|
||||
if (overrideToggle && tenantFields) {
|
||||
overrideToggle.addEventListener('change', () => {
|
||||
tenantFields.hidden = !overrideToggle.checked;
|
||||
on(overrideToggle, 'change', () => {
|
||||
if (tenantFields) {
|
||||
tenantFields.hidden = !overrideToggle.checked;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// On form submit, set save_target based on active tab
|
||||
if (settingsForm && saveTargetInput) {
|
||||
settingsForm.addEventListener('submit', () => {
|
||||
on(settingsForm, 'submit', () => {
|
||||
if (!saveTargetInput) {
|
||||
return;
|
||||
}
|
||||
const tenantPanel = settingsForm.querySelector('[data-tab-panel="tenant"]:not([hidden])');
|
||||
saveTargetInput.value = tenantPanel ? 'tenant' : 'global';
|
||||
});
|
||||
}
|
||||
|
||||
// Tenant auth mode toggle
|
||||
if (tenantAuthModeSelect && tenantBasicFields && tenantOauth2Fields) {
|
||||
tenantAuthModeSelect.addEventListener('change', () => {
|
||||
on(tenantAuthModeSelect, 'change', () => {
|
||||
if (!tenantBasicFields || !tenantOauth2Fields || !tenantAuthModeSelect) {
|
||||
return;
|
||||
}
|
||||
const isOAuth2 = tenantAuthModeSelect.value === 'oauth2';
|
||||
tenantBasicFields.hidden = isOAuth2;
|
||||
tenantOauth2Fields.hidden = !isOAuth2;
|
||||
});
|
||||
}
|
||||
|
||||
if (testButton) {
|
||||
testButton.addEventListener('click', async () => {
|
||||
on(testButton, 'click', async () => {
|
||||
setButtonLoadingState(true);
|
||||
setTestResult('info', getMessage('testLoadingLabel', 'Testing...'));
|
||||
setTestResult('info', readMessage(settingsForm, 'testLoadingLabel', 'Testing...'));
|
||||
|
||||
try {
|
||||
const csrfInput = settingsForm.querySelector('input[name="csrf_token"]');
|
||||
const csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
||||
const csrfInput = document.querySelector('input[name="csrf_token"]');
|
||||
const csrfToken = csrfInput?.value || csrfMeta?.content || '';
|
||||
|
||||
const formData = new FormData();
|
||||
const body = new URLSearchParams();
|
||||
if (csrfToken) {
|
||||
formData.append('csrf_token', csrfToken);
|
||||
body.set('csrf_token', csrfToken);
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
const data = await postForm(
|
||||
new URL('helpdesk/settings/test-connection-data', document.baseURI).href,
|
||||
{
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'same-origin',
|
||||
},
|
||||
body,
|
||||
{ signal: requestController.signal }
|
||||
);
|
||||
|
||||
const data = await parseJsonSafely(response);
|
||||
const successMessage = getMessage('testSuccessMessage', 'Connection successful');
|
||||
const defaultErrorMessage = getMessage('testErrorMessage', 'Connection failed');
|
||||
const successMessage = readMessage(settingsForm, 'testSuccessMessage', 'Connection successful');
|
||||
const defaultErrorMessage = readMessage(settingsForm, 'testErrorMessage', 'Connection failed');
|
||||
|
||||
if (response.ok && data?.ok) {
|
||||
if (data?.ok === true) {
|
||||
const message = data.message || successMessage;
|
||||
setTestResult('success', message);
|
||||
showAsyncFlash('success', message);
|
||||
@@ -136,23 +146,36 @@ if (testButton) {
|
||||
let message = defaultErrorMessage;
|
||||
if (data?.error) {
|
||||
message = data.error;
|
||||
} else if (!response.ok) {
|
||||
message = `${defaultErrorMessage} (HTTP ${response.status})`;
|
||||
}
|
||||
|
||||
if (data?.debug?.http_code) {
|
||||
message += ` (HTTP ${data.debug.http_code})`;
|
||||
}
|
||||
|
||||
setTestResult('warning', message);
|
||||
showAsyncFlash('error', message);
|
||||
}
|
||||
} catch {
|
||||
const message = getMessage('testErrorMessage', 'Connection failed');
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultErrorMessage = readMessage(settingsForm, 'testErrorMessage', 'Connection failed');
|
||||
let message = defaultErrorMessage;
|
||||
const status = error?.status ?? 0;
|
||||
if (status > 0) {
|
||||
message = `${defaultErrorMessage} (HTTP ${status})`;
|
||||
}
|
||||
|
||||
setTestResult('warning', message);
|
||||
showAsyncFlash('error', message);
|
||||
} finally {
|
||||
setButtonLoadingState(false);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
destroy: () => {
|
||||
listenerController.abort();
|
||||
requestController.abort();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user