fix: normalize dashboard api client paths

parent 3e764afb
......@@ -602,7 +602,7 @@ async function saveAutoselectItem(key) {
}
if (statusEl) statusEl.textContent = window.i18n.t('autoselect.saving');
try {
const result = await apiCall('POST', '/dashboard/api/autoselect', {
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/autoselect`, {
autoselect_id: key,
config: autoselectConfig[key]
});
......@@ -625,7 +625,7 @@ async function copyAutoselect(sourceKey) {
cloned.model_name = newKey;
autoselectConfig[newKey] = cloned;
try {
const result = await apiCall('POST', '/dashboard/api/autoselect', { autoselect_id: newKey, config: cloned });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/autoselect`, { autoselect_id: newKey, config: cloned });
if (!result.success) {
showAlert('Error copying autoselect: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete autoselectConfig[newKey];
......@@ -651,7 +651,7 @@ async function addAutoselect() {
const newCfg = { model_name: key, description: '', selection_model: '', fallback: '', available_models: [] };
autoselectConfig[key] = newCfg;
try {
const result = await apiCall('POST', '/dashboard/api/autoselect', { autoselect_id: key, config: newCfg });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/autoselect`, { autoselect_id: key, config: newCfg });
if (!result.success) {
showAlert('Error creating autoselect: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete autoselectConfig[key];
......@@ -670,7 +670,7 @@ async function addAutoselect() {
async function removeAutoselect(key) {
if (await showDangerConfirm(window.i18n.interpolate(window.i18n.t('autoselect.remove_confirm'), {key}), window.i18n.t('autoselect.remove_title'))) {
try {
const result = await apiCall('DELETE', '/dashboard/api/autoselect/' + encodeURIComponent(key));
const result = await apiCall('DELETE', `${BASE_PATH}/dashboard/api/autoselect/${encodeURIComponent(key)}`);
if (!result.success) { showAlert('Error: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger'); return; }
} catch (e) { showAlert('Error: ' + e.message, 'Error', '❌', 'danger'); return; }
delete autoselectConfig[key];
......
......@@ -2530,7 +2530,7 @@ async function saveProvider(key) {
const statusEl = document.getElementById(`save-status-${key}`);
if (statusEl) statusEl.textContent = window.i18n.t('providers.saving');
try {
const result = await apiCall('POST', '/dashboard/api/provider', {
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/provider`, {
provider_id: key,
config: providersData[key]
});
......@@ -2547,7 +2547,7 @@ async function saveProvider(key) {
async function removeProvider(key) {
if (await showDangerConfirm(window.i18n.interpolate(window.i18n.t('providers.remove_provider_confirm'), {key}), window.i18n.t('providers.remove_provider_title'))) {
try {
const result = await apiCall('DELETE', '/dashboard/api/provider/' + encodeURIComponent(key));
const result = await apiCall('DELETE', `${BASE_PATH}/dashboard/api/provider/${encodeURIComponent(key)}`);
if (!result.success) {
showAlert('Error removing provider: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
return;
......@@ -2714,7 +2714,7 @@ async function rotateCoderAIToken(key) {
statusEl.style.color = 'var(--color-muted)';
}
try {
const result = await apiCall('POST', `/dashboard/api/provider/${encodeURIComponent(key)}/coderai-token`, {});
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/provider/${encodeURIComponent(key)}/coderai-token`, {});
if (!result.success) throw new Error(result.error || 'Unknown error');
if (!providersData[key].coderai_config) providersData[key].coderai_config = {};
providersData[key].coderai_config.registration_token = result.registration_token;
......@@ -2883,7 +2883,7 @@ async function confirmAddProvider() {
// Immediately persist the new provider
try {
const result = await apiCall('POST', '/dashboard/api/provider', {
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/provider`, {
provider_id: key,
config: providersData[key]
});
......
......@@ -556,7 +556,7 @@ async function saveRotation(key) {
const statusEl = document.getElementById(`rot-save-status-${key}`);
if (statusEl) statusEl.textContent = window.i18n.t('rotations.saving');
try {
const result = await apiCall('POST', '/dashboard/api/rotation', {
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/rotation`, {
rotation_id: key,
config: rotationsConfig.rotations[key]
});
......@@ -579,7 +579,7 @@ async function copyRotation(sourceKey) {
cloned.model_name = newKey;
rotationsConfig.rotations[newKey] = cloned;
try {
const result = await apiCall('POST', '/dashboard/api/rotation', { rotation_id: newKey, config: cloned });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/rotation`, { rotation_id: newKey, config: cloned });
if (!result.success) {
showAlert('Error copying rotation: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete rotationsConfig.rotations[newKey];
......@@ -606,7 +606,7 @@ async function addRotation() {
const newCfg = { model_name: key, notifyerrors: false, capabilities: [], providers: [] };
rotationsConfig.rotations[key] = newCfg;
try {
const result = await apiCall('POST', '/dashboard/api/rotation', { rotation_id: key, config: newCfg });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/rotation`, { rotation_id: key, config: newCfg });
if (!result.success) {
showAlert('Error creating rotation: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete rotationsConfig.rotations[key];
......@@ -625,7 +625,7 @@ async function addRotation() {
async function removeRotation(key) {
if (await showDangerConfirm(window.i18n.interpolate(window.i18n.t('rotations.remove_confirm'), {key}), window.i18n.t('rotations.remove_title'))) {
try {
const result = await apiCall('DELETE', '/dashboard/api/rotation/' + encodeURIComponent(key));
const result = await apiCall('DELETE', `${BASE_PATH}/dashboard/api/rotation/${encodeURIComponent(key)}`);
if (!result.success) { showAlert('Error: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger'); return; }
} catch (e) { showAlert('Error: ' + e.message, 'Error', '❌', 'danger'); return; }
delete rotationsConfig.rotations[key];
......
......@@ -609,7 +609,7 @@ async function saveAutoselectItem(key) {
const statusEl = document.getElementById(`asel-save-status-${key}`);
if (statusEl) statusEl.textContent = window.i18n.t('autoselect.saving');
try {
const result = await apiCall('POST', '/dashboard/api/autoselect', {
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/autoselect`, {
autoselect_id: key,
config: autoselectConfig[key]
});
......@@ -632,7 +632,7 @@ async function copyAutoselect(sourceKey) {
cloned.model_name = newKey;
autoselectConfig[newKey] = cloned;
try {
const result = await apiCall('POST', '/dashboard/api/autoselect', { autoselect_id: newKey, config: cloned });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/autoselect`, { autoselect_id: newKey, config: cloned });
if (!result.success) {
showAlert('Error copying autoselect: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete autoselectConfig[newKey];
......@@ -658,7 +658,7 @@ async function addAutoselect() {
const newCfg = { model_name: key, description: '', selection_model: '', fallback: '', available_models: [] };
autoselectConfig[key] = newCfg;
try {
const result = await apiCall('POST', '/dashboard/api/autoselect', { autoselect_id: key, config: newCfg });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/autoselect`, { autoselect_id: key, config: newCfg });
if (!result.success) {
showAlert('Error creating autoselect: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete autoselectConfig[key];
......@@ -677,7 +677,7 @@ async function addAutoselect() {
async function removeAutoselect(key) {
if (await showDangerConfirm(window.i18n.interpolate(window.i18n.t('autoselect.remove_confirm'), {key}), window.i18n.t('autoselect.remove_title'))) {
try {
const result = await apiCall('DELETE', '/dashboard/api/autoselect/' + encodeURIComponent(key));
const result = await apiCall('DELETE', `${BASE_PATH}/dashboard/api/autoselect/${encodeURIComponent(key)}`);
if (!result.success) { showAlert('Error: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger'); return; }
} catch (e) { showAlert('Error: ' + e.message, 'Error', '❌', 'danger'); return; }
delete autoselectConfig[key];
......
......@@ -561,7 +561,7 @@ async function saveRotation(key) {
const statusEl = document.getElementById(`rot-save-status-${key}`);
if (statusEl) statusEl.textContent = window.i18n.t('rotations.saving');
try {
const result = await apiCall('POST', '/dashboard/api/rotation', {
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/rotation`, {
rotation_id: key,
config: rotationsConfig.rotations[key]
});
......@@ -584,7 +584,7 @@ async function copyRotation(sourceKey) {
cloned.model_name = newKey;
rotationsConfig.rotations[newKey] = cloned;
try {
const result = await apiCall('POST', '/dashboard/api/rotation', { rotation_id: newKey, config: cloned });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/rotation`, { rotation_id: newKey, config: cloned });
if (!result.success) {
showAlert('Error copying rotation: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete rotationsConfig.rotations[newKey];
......@@ -611,7 +611,7 @@ async function addRotation() {
const newCfg = { model_name: key, notifyerrors: false, capabilities: [], providers: [] };
rotationsConfig.rotations[key] = newCfg;
try {
const result = await apiCall('POST', '/dashboard/api/rotation', { rotation_id: key, config: newCfg });
const result = await apiCall('POST', `${BASE_PATH}/dashboard/api/rotation`, { rotation_id: key, config: newCfg });
if (!result.success) {
showAlert('Error creating rotation: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger');
delete rotationsConfig.rotations[key];
......@@ -630,7 +630,7 @@ async function addRotation() {
async function removeRotation(key) {
if (await showDangerConfirm(window.i18n.interpolate(window.i18n.t('rotations.remove_confirm'), {key}), window.i18n.t('rotations.remove_title'))) {
try {
const result = await apiCall('DELETE', '/dashboard/api/rotation/' + encodeURIComponent(key));
const result = await apiCall('DELETE', `${BASE_PATH}/dashboard/api/rotation/${encodeURIComponent(key)}`);
if (!result.success) { showAlert('Error: ' + (result.error || 'Unknown'), 'Error', '❌', 'danger'); return; }
} catch (e) { showAlert('Error: ' + e.message, 'Error', '❌', 'danger'); return; }
delete rotationsConfig.rotations[key];
......
......@@ -232,6 +232,10 @@ def test_admin_providers_template_uses_single_proxy_aware_base_path(monkeypatch)
assert capture.calls[-1]["name"] == "dashboard/providers.html"
assert response.text.count('const BASE_PATH = "/proxy/app";') == 1
assert 'window.location.href = `${BASE_PATH}/dashboard/providers?success=1`;' in response.text
assert "`${BASE_PATH}/dashboard/api/provider`" in response.text
assert "apiCall('DELETE', `${BASE_PATH}/dashboard/api/provider/${encodeURIComponent(key)}`)" in response.text
assert "apiCall('POST', '/dashboard/api/provider'" not in response.text
assert "apiCall('DELETE', '/dashboard/api/provider/" not in response.text
def test_user_rotations_page_uses_proxy_aware_search_urls(monkeypatch):
......@@ -249,7 +253,11 @@ def test_user_rotations_page_uses_proxy_aware_search_urls(monkeypatch):
assert response.status_code == 200
assert 'const BASE_PATH = "/proxy/app"' in response.text
assert "fetch(`${BASE_PATH}/dashboard/providers/" in response.text
assert "`${BASE_PATH}/dashboard/api/rotation`" in response.text
assert "`${BASE_PATH}/dashboard/api/rotation/${encodeURIComponent(key)}`" in response.text
assert "fetch('/dashboard/providers/" not in response.text
assert "apiCall('POST', '/dashboard/api/rotation'" not in response.text
assert "apiCall('DELETE', '/dashboard/api/rotation/" not in response.text
def test_admin_autoselect_template_uses_proxy_aware_cancel_link():
......@@ -274,6 +282,10 @@ def test_admin_autoselect_template_uses_proxy_aware_cancel_link():
assert '<a href="/dashboard" class="btn btn-secondary">Cancel</a>' not in response_text
assert '<a href="/proxy/app/dashboard" class="btn btn-secondary">Cancel</a>' in response_text
assert "`${BASE_PATH}/dashboard/api/autoselect`" in response_text
assert "`${BASE_PATH}/dashboard/api/autoselect/${encodeURIComponent(key)}`" in response_text
assert "apiCall('POST', '/dashboard/api/autoselect'" not in response_text
assert "apiCall('DELETE', '/dashboard/api/autoselect/" not in response_text
def test_market_page_uses_proxy_aware_market_api_urls(monkeypatch):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment