fix: normalize dashboard api client paths

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