Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
A
aisbf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexlab
aisbf
Commits
a8b5285c
Commit
a8b5285c
authored
May 12, 2026
by
Stefy Lanza (nextime / spora )
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Normalize remaining dashboard proxy URLs
parent
2313402a
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
118 additions
and
5 deletions
+118
-5
analytics.html
templates/dashboard/analytics.html
+4
-3
users.html
templates/dashboard/users.html
+3
-2
test_dashboard_proxy_awareness.py
tests/routes/test_dashboard_proxy_awareness.py
+90
-0
test_dashboard_user_market_controls.py
tests/routes/test_dashboard_user_market_controls.py
+21
-0
No files found.
templates/dashboard/analytics.html
View file @
a8b5285c
...
@@ -222,6 +222,7 @@ const analyticsData = {
...
@@ -222,6 +222,7 @@ const analyticsData = {
</script>
</script>
<script>
<script>
const
BASE_PATH
=
{{
request
.
scope
.
get
(
'root_path'
,
''
)
|
tojson
}};
document
.
getElementById
(
'timeRangeSelect'
).
addEventListener
(
'change'
,
function
()
{
document
.
getElementById
(
'timeRangeSelect'
).
addEventListener
(
'change'
,
function
()
{
var
customRange
=
document
.
getElementById
(
'customDateRange'
);
var
customRange
=
document
.
getElementById
(
'customDateRange'
);
if
(
this
.
value
===
'custom'
)
{
if
(
this
.
value
===
'custom'
)
{
...
@@ -287,7 +288,7 @@ function escHtml(s) {
...
@@ -287,7 +288,7 @@ function escHtml(s) {
// Search users via API
// Search users via API
async
function
searchUsers
(
query
)
{
async
function
searchUsers
(
query
)
{
try
{
try
{
const
response
=
await
fetch
(
`/api/users/search?q=
${
encodeURIComponent
(
query
)}
`
);
const
response
=
await
fetch
(
`
${
BASE_PATH
}
/api/users/search?q=
${
encodeURIComponent
(
query
)}
`
);
const
data
=
await
response
.
json
();
const
data
=
await
response
.
json
();
return
data
.
users
||
[];
return
data
.
users
||
[];
}
catch
(
error
)
{
}
catch
(
error
)
{
...
@@ -1033,7 +1034,7 @@ function confirmDeleteAnalytics(scope) {
...
@@ -1033,7 +1034,7 @@ function confirmDeleteAnalytics(scope) {
:
'Delete ALL analytics including all user data? This cannot be undone.'
;
:
'Delete ALL analytics including all user data? This cannot be undone.'
;
if
(
!
confirm
(
msg
))
return
;
if
(
!
confirm
(
msg
))
return
;
if
(
!
confirm
(
'Are you sure? This is irreversible.'
))
return
;
if
(
!
confirm
(
'Are you sure? This is irreversible.'
))
return
;
fetch
(
'/api/admin/analytics/delete-'
+
scope
,
{
method
:
'POST'
})
fetch
(
`
${
BASE_PATH
}
/api/admin/analytics/delete-
${
scope
}
`
,
{
method
:
'POST'
})
.
then
(
r
=>
r
.
json
())
.
then
(
r
=>
r
.
json
())
.
then
(
d
=>
{
.
then
(
d
=>
{
alert
(
'Deleted '
+
d
.
deleted
+
' records.'
);
alert
(
'Deleted '
+
d
.
deleted
+
' records.'
);
...
@@ -1045,7 +1046,7 @@ function confirmDeleteAnalytics(scope) {
...
@@ -1045,7 +1046,7 @@ function confirmDeleteAnalytics(scope) {
{% endif %}
{% endif %}
<div
style=
"margin-top: 30px; display: flex; gap: 10px; flex-wrap: wrap;"
>
<div
style=
"margin-top: 30px; display: flex; gap: 10px; flex-wrap: wrap;"
>
<a
href=
"
/dashboard
"
class=
"btn btn-secondary"
data-i18n=
"analytics_page.back"
>
Back to Dashboard
</a>
<a
href=
"
{{ url_for(request, '/dashboard') }}
"
class=
"btn btn-secondary"
data-i18n=
"analytics_page.back"
>
Back to Dashboard
</a>
<a
href=
"{{ url_for(request, '/dashboard/analytics/provider-quotas') }}"
class=
"btn btn-secondary"
>
Provider Quota Debug
</a>
<a
href=
"{{ url_for(request, '/dashboard/analytics/provider-quotas') }}"
class=
"btn btn-secondary"
>
Provider Quota Debug
</a>
{% if is_admin %}
{% if is_admin %}
<a
href=
"{{ url_for(request, '/dashboard/rate-limits') }}"
class=
"btn"
>
Rate Limits
</a>
<a
href=
"{{ url_for(request, '/dashboard/rate-limits') }}"
class=
"btn"
>
Rate Limits
</a>
...
...
templates/dashboard/users.html
View file @
a8b5285c
...
@@ -301,6 +301,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -301,6 +301,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script>
<script>
// Utility functions
// Utility functions
const
BASE_PATH
=
{{
request
.
scope
.
get
(
'root_path'
,
''
)
|
tojson
}};
const
baseUrl
=
document
.
getElementById
(
'url-data'
).
dataset
.
baseUrl
;
const
baseUrl
=
document
.
getElementById
(
'url-data'
).
dataset
.
baseUrl
;
function
getUrlParams
()
{
function
getUrlParams
()
{
const
params
=
new
URLSearchParams
(
window
.
location
.
search
);
const
params
=
new
URLSearchParams
(
window
.
location
.
search
);
...
@@ -773,7 +774,7 @@ async function performBulkAction(action, description, destructive = false, extra
...
@@ -773,7 +774,7 @@ async function performBulkAction(action, description, destructive = false, extra
requestData
.
extra_data
=
extraData
;
requestData
.
extra_data
=
extraData
;
}
}
fetch
(
'/dashboard/users/bulk'
,
{
fetch
(
`
${
BASE_PATH
}
/dashboard/users/bulk`
,
{
method
:
'POST'
,
method
:
'POST'
,
headers
:
{
headers
:
{
'Content-Type'
:
'application/json'
,
'Content-Type'
:
'application/json'
,
...
@@ -1092,7 +1093,7 @@ async function submitNotification() {
...
@@ -1092,7 +1093,7 @@ async function submitNotification() {
return
;
return
;
}
}
try
{
try
{
const
res
=
await
fetch
(
'/dashboard/api/admin/notifications/send'
,
{
const
res
=
await
fetch
(
`
${
BASE_PATH
}
/dashboard/api/admin/notifications/send`
,
{
method
:
'POST'
,
method
:
'POST'
,
headers
:
{
'Content-Type'
:
'application/json'
},
headers
:
{
'Content-Type'
:
'application/json'
},
body
:
JSON
.
stringify
({
user_ids
:
_notifyUserIds
,
title
,
message
})
body
:
JSON
.
stringify
({
user_ids
:
_notifyUserIds
,
title
,
message
})
...
...
tests/routes/test_dashboard_proxy_awareness.py
View file @
a8b5285c
...
@@ -10,6 +10,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
...
@@ -10,6 +10,7 @@ from jinja2 import Environment, FileSystemLoader, select_autoescape
from
aisbf.routes.dashboard
import
market
as
dashboard_market
from
aisbf.routes.dashboard
import
market
as
dashboard_market
from
aisbf.routes.dashboard
import
providers
as
dashboard_providers
from
aisbf.routes.dashboard
import
providers
as
dashboard_providers
from
aisbf.routes.dashboard
import
settings
as
dashboard_settings
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
resolve
()
.
parents
[
2
]))
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
resolve
()
.
parents
[
2
]))
from
main
import
app
from
main
import
app
...
@@ -151,6 +152,25 @@ class RegistryStub:
...
@@ -151,6 +152,25 @@ class RegistryStub:
return
self
.
_db
return
self
.
_db
class
DashboardUsersDbStub
:
def
__init__
(
self
):
self
.
paginated_calls
=
[]
def
get_users_paginated
(
self
,
**
kwargs
):
self
.
paginated_calls
.
append
(
kwargs
)
return
{
"users"
:
[{
"id"
:
1
,
"username"
:
"alice"
,
"display_name"
:
"alice"
,
"email"
:
"alice@example.test"
,
"role"
:
"user"
,
"tier_id"
:
10
,
"created_by"
:
"admin"
,
"created_at"
:
"2026-01-01"
,
"last_login"
:
None
,
"is_active"
:
True
}],
"total"
:
1
,
}
def
get_all_tiers
(
self
):
return
[{
"id"
:
10
,
"name"
:
"Pro"
,
"is_visible"
:
True
}]
class
DashboardSettingsRegistryStub
(
RegistryStub
):
pass
def
_find_session_secret
()
->
str
:
def
_find_session_secret
()
->
str
:
for
middleware
in
app
.
user_middleware
:
for
middleware
in
app
.
user_middleware
:
kwargs
=
getattr
(
middleware
,
"kwargs"
,
{})
kwargs
=
getattr
(
middleware
,
"kwargs"
,
{})
...
@@ -303,3 +323,73 @@ def test_market_page_uses_proxy_aware_market_api_urls(monkeypatch):
...
@@ -303,3 +323,73 @@ def test_market_page_uses_proxy_aware_market_api_urls(monkeypatch):
assert
'const BASE_PATH = "/proxy/app"'
in
response
.
text
assert
'const BASE_PATH = "/proxy/app"'
in
response
.
text
assert
"fetch(`${BASE_PATH}/api/market/listings`"
in
response
.
text
assert
"fetch(`${BASE_PATH}/api/market/listings`"
in
response
.
text
assert
"fetch('/api/market/listings'"
not
in
response
.
text
assert
"fetch('/api/market/listings'"
not
in
response
.
text
def
test_dashboard_users_page_uses_proxy_aware_bulk_and_notification_urls
(
monkeypatch
):
db
=
DashboardUsersDbStub
()
capture
=
TemplateCapture
()
client
=
TestClient
(
app
)
_login_as_admin
(
client
)
monkeypatch
.
setattr
(
dashboard_settings
,
"DatabaseRegistry"
,
DashboardSettingsRegistryStub
(
db
))
monkeypatch
.
setattr
(
dashboard_settings
,
"_templates"
,
capture
)
response
=
client
.
get
(
"/dashboard/users"
,
headers
=
_forwarded_prefix_headers
())
assert
response
.
status_code
==
200
assert
capture
.
calls
[
-
1
][
"name"
]
==
"dashboard/users.html"
assert
'const BASE_PATH = "/proxy/app"'
in
response
.
text
assert
"fetch(`${BASE_PATH}/dashboard/users/bulk`"
in
response
.
text
assert
"fetch(`${BASE_PATH}/dashboard/api/admin/notifications/send`"
in
response
.
text
assert
"fetch('/dashboard/users/bulk'"
not
in
response
.
text
assert
"fetch('/dashboard/api/admin/notifications/send'"
not
in
response
.
text
def
test_analytics_page_uses_proxy_aware_search_delete_and_back_urls
(
monkeypatch
):
capture
=
TemplateCapture
()
request
=
type
(
"Req"
,
(),
{
"scope"
:
{
"root_path"
:
"/proxy/app"
},
"session"
:
{}})()
response
=
capture
.
TemplateResponse
(
request
=
request
,
name
=
"dashboard/analytics.html"
,
context
=
{
"request"
:
request
,
"selected_time_range"
:
"24h"
,
"from_date"
:
None
,
"to_date"
:
None
,
"date_range_usage"
:
None
,
"currency_symbol"
:
"$"
,
"available_providers"
:
[],
"available_models"
:
[],
"available_rotations"
:
[],
"available_autoselects"
:
[],
"available_users"
:
[
{
"id"
:
idx
,
"username"
:
f
"user{idx}"
,
"role"
:
"user"
}
for
idx
in
range
(
1
,
26
)
],
"selected_provider"
:
None
,
"selected_model"
:
None
,
"selected_rotation"
:
None
,
"selected_autoselect"
:
None
,
"selected_user"
:
None
,
"global_only"
:
""
,
"is_admin"
:
True
,
"is_config_admin"
:
True
,
"provider_stats"
:
[],
"cost_overview"
:
{
"total_estimated_cost_today"
:
0
,
"date_range"
:
None
,
"providers"
:
[]},
"optimization_savings"
:
None
,
"model_performance"
:
[],
"token_over_time"
:
"[]"
,
"rotation_breakdown"
:
[],
"autoselect_breakdown"
:
[],
},
)
response_text
=
response
.
body
.
decode
()
assert
'const BASE_PATH = "/proxy/app"'
in
response_text
assert
"fetch(`${BASE_PATH}/api/users/search?q=${encodeURIComponent(query)}`)"
in
response_text
assert
"fetch(`${BASE_PATH}/api/admin/analytics/delete-${scope}`, {method: 'POST'})"
in
response_text
assert
'<a href="/proxy/app/dashboard" class="btn btn-secondary" data-i18n="analytics_page.back">Back to Dashboard</a>'
in
response_text
assert
"fetch(`/api/users/search?q=${encodeURIComponent(query)}`)"
not
in
response_text
assert
"fetch('/api/admin/analytics/delete-' + scope"
not
in
response_text
assert
'<a href="/dashboard" class="btn btn-secondary" data-i18n="analytics_page.back">Back to Dashboard</a>'
not
in
response_text
tests/routes/test_dashboard_user_market_controls.py
View file @
a8b5285c
...
@@ -101,6 +101,10 @@ class RegistryStub:
...
@@ -101,6 +101,10 @@ class RegistryStub:
return
self
.
_db
return
self
.
_db
class
DashboardSettingsRegistryStub
(
RegistryStub
):
pass
class
AsyncJsonRequest
:
class
AsyncJsonRequest
:
def
__init__
(
self
,
session
,
body
):
def
__init__
(
self
,
session
,
body
):
self
.
session
=
session
self
.
session
=
session
...
@@ -349,3 +353,20 @@ def test_dashboard_context_middleware_hides_market_link_when_market_disabled(mon
...
@@ -349,3 +353,20 @@ def test_dashboard_context_middleware_hides_market_link_when_market_disabled(mon
assert
response
.
status_code
==
200
assert
response
.
status_code
==
200
assert
templates
.
calls
[
-
1
][
"request"
]
.
state
.
market_enabled
is
False
assert
templates
.
calls
[
-
1
][
"request"
]
.
state
.
market_enabled
is
False
assert
">Market</a>"
not
in
response
.
text
assert
">Market</a>"
not
in
response
.
text
def
test_admin_payment_settings_uses_proxy_aware_market_admin_link
(
monkeypatch
):
db
=
MarketSettingsDbStub
()
templates
=
TemplateCapture
()
templates
.
_env
.
globals
[
"url_for"
]
=
lambda
request
,
path
,
**
kwargs
:
f
"{request.scope.get('root_path', '')}{path}"
client
=
TestClient
(
app
)
_login_as_admin
(
client
)
monkeypatch
.
setattr
(
dashboard_admin
,
"DatabaseRegistry"
,
RegistryStub
(
db
))
monkeypatch
.
setattr
(
dashboard_admin
,
"_templates"
,
templates
)
response
=
client
.
get
(
"/dashboard/admin/payment-settings"
,
headers
=
{
"X-Forwarded-Prefix"
:
"/proxy/app"
})
assert
response
.
status_code
==
200
assert
'href="/proxy/app/dashboard/admin/market"'
in
response
.
text
assert
'href="/dashboard/admin/market"'
not
in
response
.
text
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment