Commit e7663cb5 authored by Your Name's avatar Your Name

Update version 0.99.31

parent 0a61873a
......@@ -54,7 +54,7 @@ from .auth.qwen import QwenOAuth2
from .handlers import RequestHandler, RotationHandler, AutoselectHandler
from .utils import count_messages_tokens, split_messages_into_chunks, get_max_request_tokens_for_model
__version__ = "0.99.29"
__version__ = "0.99.31"
__all__ = [
# Config
"config",
......
......@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "aisbf"
version = "0.99.29"
version = "0.99.31"
description = "AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations"
readme = "README.md"
license = "GPL-3.0-or-later"
......
......@@ -49,7 +49,7 @@ class InstallCommand(_install):
setup(
name="aisbf",
version="0.99.29",
version="0.99.31",
author="AISBF Contributors",
author_email="stefy@nexlab.net",
description="AISBF - AI Service Broker Framework || AI Should Be Free - A modular proxy server for managing multiple AI provider integrations",
......
......@@ -213,6 +213,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% endif %}
</div>
<script>
const analyticsData = {
selectedUser: {{ selected_user or 'null' }},
availableUsers: {{ available_users|tojson }}
};
</script>
<script>
document.getElementById('timeRangeSelect').addEventListener('change', function() {
var customRange = document.getElementById('customDateRange');
......@@ -262,16 +269,15 @@ document.getElementById('timeRangeSelect').addEventListener('change', function()
const resultsDiv = document.getElementById('userSearchResults');
const hiddenInput = document.getElementById('userFilterValue');
let debounceTimer;
let selectedUserId = {{ selected_user or 'null' }};
let selectedUserId = analyticsData.selectedUser;
// Set initial display value if a user is selected
{% if selected_user %}
{% for user in available_users %}
{% if user.id == selected_user %}
searchInput.value = '{{ user.username }}{% if user.role == "admin" %} (admin){% endif %}';
{% endif %}
{% endfor %}
{% endif %}
if (analyticsData.selectedUser) {
const user = analyticsData.availableUsers.find(u => u.id == analyticsData.selectedUser);
if (user) {
searchInput.value = user.username + (user.role === 'admin' ? ' (admin)' : '');
}
}
// Search users via API
async function searchUsers(query) {
......
......@@ -40,9 +40,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
<script>
let autoselectConfig = {{ autoselect_json | safe }};
let availableRotations = {{ available_rotations | safe }};
let availableModels = {{ available_models | safe }};
const autoselectData = {
config: {{ autoselect_json | safe }},
rotations: {{ available_rotations | safe }},
models: {{ available_models | safe }},
saveUrl: "{{ url_for(request, '/dashboard/autoselect') }}",
successUrl: "{{ url_for(request, '/dashboard/autoselect?success=1') }}"
};
let autoselectConfig = autoselectData.config;
let availableRotations = autoselectData.rotations;
let availableModels = autoselectData.models;
let expandedAutoselects = new Set();
function renderAutoselectList() {
......@@ -381,7 +388,7 @@ function updateAutoselectModel(autoselectKey, index, field, value) {
async function saveAutoselect() {
try {
const response = await fetch('{{ url_for(request, "/dashboard/autoselect") }}', {
const response = await fetch(autoselectData.saveUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
......@@ -390,7 +397,7 @@ async function saveAutoselect() {
});
if (response.ok) {
window.location.href = '{{ url_for(request, "/dashboard/autoselect?success=1") }}';
window.location.href = autoselectData.successUrl;
} else {
alert('Error saving configuration');
}
......
......@@ -21,6 +21,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% block content %}
<h2 style="margin-bottom: 30px;">Adaptive Rate Limits</h2>
<div id="rate-limits-data" data-data-url="{{ url_for(request, '/dashboard/rate-limits/data') }}" style="display:none;"></div>
<!-- Quick Links Navigation -->
<div style="background: #1a1a2e; padding: 15px; border-radius: 8px; margin-bottom: 30px;">
<div style="display: flex; gap: 15px; flex-wrap: wrap;">
......@@ -93,12 +95,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</style>
<script>
const dataUrl = document.getElementById('rate-limits-data').dataset.dataUrl;
async function loadRateLimits() {
const content = document.getElementById('rate-limits-content');
content.innerHTML = '<p>Loading rate limit data...</p>';
try {
const response = await fetch('{{ url_for(request, "/dashboard/rate-limits/data") }}');
const response = await fetch(dataUrl);
const data = await response.json();
if (Object.keys(data).length === 0) {
......@@ -196,7 +200,7 @@ async function clearAllRateLimiters() {
// First get the list of providers
try {
const response = await fetch('{{ url_for(request, "/dashboard/rate-limits/data") }}');
const response = await fetch(dataUrl);
const data = await response.json();
for (const providerId of Object.keys(data)) {
......
......@@ -47,8 +47,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
</div>
<script>
let rotationsConfig = {{ rotations_json | safe }};
let availableProviders = {{ available_providers | safe }};
const rotationsData = {
config: {{ rotations_json | safe }},
providers: {{ available_providers | safe }},
saveUrl: "{{ url_for(request, '/dashboard/rotations') }}",
successUrl: "{{ url_for(request, '/dashboard/rotations?success=1') }}"
};
let rotationsConfig = rotationsData.config;
let availableProviders = rotationsData.providers;
let expandedRotations = new Set();
document.getElementById('global-notify').checked = rotationsConfig.notifyerrors || false;
......@@ -376,7 +382,7 @@ function updateRotationModelCondenseMethod(rotationKey, providerIndex, modelInde
async function saveRotations() {
try {
const response = await fetch('{{ url_for(request, "/dashboard/rotations") }}', {
const response = await fetch(rotationsData.saveUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
......@@ -385,7 +391,7 @@ async function saveRotations() {
});
if (response.ok) {
window.location.href = '{{ url_for(request, "/dashboard/rotations?success=1") }}';
window.location.href = rotationsData.successUrl;
} else {
alert('Error saving configuration');
}
......
......@@ -21,6 +21,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
{% block content %}
<h2 style="margin-bottom: 30px;">User Management</h2>
<div id="url-data" data-base-url="{{ url_for(request, '/dashboard/users/') }}" style="display:none;"></div>
{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}
......@@ -280,6 +282,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script>
// Utility functions
const baseUrl = document.getElementById('url-data').dataset.baseUrl;
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
return {
......@@ -335,6 +338,14 @@ function updateUsers(params) {
tableContainer.innerHTML = originalContent;
document.querySelector('table').replaceWith(newTable);
// Reset select-all state
const selectAll = document.getElementById('select-all');
if (selectAll) {
selectAll.checked = false;
selectAll.indeterminate = false;
}
updateBulkActionsVisibility();
const existingPagination = document.querySelector('[style*="margin-top: 20px"]');
if (existingPagination) {
existingPagination.replaceWith(newPagination);
......@@ -414,9 +425,34 @@ document.addEventListener('DOMContentLoaded', function() {
document.querySelector('table').after(newPagination);
}
// Reset select-all state
const selectAll = document.getElementById('select-all');
if (selectAll) {
selectAll.checked = false;
selectAll.indeterminate = false;
}
updateBulkActionsVisibility();
// Re-attach event listeners to new elements
attachPaginationListeners();
attachSortingListeners();
// Reattach select-all listener
if (selectAll) {
selectAll.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.user-checkbox');
checkboxes.forEach(cb => cb.checked = this.checked);
updateBulkActionsVisibility();
});
}
// Reattach individual checkbox listeners
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
updateBulkActionsVisibility();
updateSelectAllState();
});
});
})
.catch(error => {
console.error('Error updating users:', error);
......@@ -529,7 +565,33 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('select-all').addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.user-checkbox');
checkboxes.forEach(cb => cb.checked = this.checked);
updateBulkActionsVisibility();
updateBulkActionsVisibility();
});
// Individual checkbox change handlers
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
updateBulkActionsVisibility();
updateSelectAllState();
});
});
// Bulk action button handlers
document.getElementById('bulk-enable')?.addEventListener('click', function() {
performBulkAction('enable', 'enable these users');
});
document.getElementById('bulk-disable')?.addEventListener('click', function() {
performBulkAction('disable', 'disable these users');
});
document.getElementById('bulk-delete')?.addEventListener('click', function() {
performBulkAction('delete', 'delete these users', true);
});
document.getElementById('bulk-clear')?.addEventListener('click', function() {
clearSelection();
});
});
// Function to attach event listeners to dynamic elements
......@@ -576,6 +638,24 @@ function attachEventListeners() {
updateUsers({ limit: this.value, page: 1 });
});
}
// Reattach select-all listener
const selectAll = document.getElementById('select-all');
if (selectAll) {
selectAll.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.user-checkbox');
checkboxes.forEach(cb => cb.checked = this.checked);
updateBulkActionsVisibility();
});
}
// Reattach individual checkbox listeners
document.querySelectorAll('.user-checkbox').forEach(checkbox => {
checkbox.addEventListener('change', function() {
updateBulkActionsVisibility();
updateSelectAllState();
});
});
}
function updateBulkActionsVisibility() {
......@@ -666,7 +746,7 @@ function editUser(userId, username, email, role, isActive) {
document.getElementById('edit-email').value = email;
document.getElementById('edit-role').value = role;
document.getElementById('edit-is-active').checked = isActive;
document.getElementById('edit-form').action = '{{ url_for(request, "/dashboard/users/") }}' + userId + '/edit';
document.getElementById('edit-form').action = baseUrl + userId + '/edit';
document.getElementById('edit-modal').style.display = 'block';
}
......@@ -677,7 +757,7 @@ function closeEditModal() {
function toggleUserStatus(userId, currentStatus) {
const action = currentStatus ? 'disable' : 'enable';
if (confirm('Are you sure you want to ' + action + ' this user?')) {
fetch('{{ url_for(request, "/dashboard/users/") }}' + userId + '/toggle', {
fetch(baseUrl + userId + '/toggle', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
})
......@@ -697,7 +777,7 @@ function toggleUserStatus(userId, currentStatus) {
function deleteUser(userId, username) {
if (confirm('Are you sure you want to delete user "' + username + '"? This action cannot be undone.')) {
fetch('{{ url_for(request, "/dashboard/users/") }}' + userId + '/delete', {
fetch(baseUrl + userId + '/delete', {
method: 'POST',
headers: {'Content-Type': 'application/json'}
})
......@@ -716,7 +796,7 @@ function deleteUser(userId, username) {
}
function updateUserTier(userId, tierId) {
fetch('{{ url_for(request, "/dashboard/users/") }}' + userId + '/tier', {
fetch(baseUrl + userId + '/tier', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ tier_id: parseInt(tierId) })
......
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