django-cfg 1.3.7__py3-none-any.whl → 1.3.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/__init__.py +24 -8
- django_cfg/apps/accounts/admin/activity_admin.py +146 -0
- django_cfg/apps/accounts/admin/filters.py +98 -22
- django_cfg/apps/accounts/admin/group_admin.py +86 -0
- django_cfg/apps/accounts/admin/inlines.py +42 -13
- django_cfg/apps/accounts/admin/otp_admin.py +115 -0
- django_cfg/apps/accounts/admin/registration_admin.py +173 -0
- django_cfg/apps/accounts/admin/resources.py +123 -19
- django_cfg/apps/accounts/admin/twilio_admin.py +327 -0
- django_cfg/apps/accounts/admin/user_admin.py +362 -0
- django_cfg/apps/agents/admin/__init__.py +17 -4
- django_cfg/apps/agents/admin/execution_admin.py +204 -183
- django_cfg/apps/agents/admin/registry_admin.py +230 -255
- django_cfg/apps/agents/admin/toolsets_admin.py +274 -321
- django_cfg/apps/agents/core/__init__.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +221 -0
- django_cfg/apps/agents/core/exceptions.py +14 -0
- django_cfg/apps/agents/core/orchestrator.py +18 -3
- django_cfg/apps/knowbase/admin/__init__.py +1 -1
- django_cfg/apps/knowbase/admin/archive_admin.py +352 -640
- django_cfg/apps/knowbase/admin/chat_admin.py +258 -192
- django_cfg/apps/knowbase/admin/document_admin.py +269 -262
- django_cfg/apps/knowbase/admin/external_data_admin.py +271 -489
- django_cfg/apps/knowbase/config/settings.py +21 -4
- django_cfg/apps/knowbase/views/chat_views.py +3 -0
- django_cfg/apps/leads/admin/__init__.py +3 -1
- django_cfg/apps/leads/admin/leads_admin.py +235 -35
- django_cfg/apps/maintenance/admin/__init__.py +2 -2
- django_cfg/apps/maintenance/admin/api_key_admin.py +125 -63
- django_cfg/apps/maintenance/admin/log_admin.py +143 -61
- django_cfg/apps/maintenance/admin/scheduled_admin.py +212 -301
- django_cfg/apps/maintenance/admin/site_admin.py +213 -352
- django_cfg/apps/newsletter/admin/__init__.py +29 -2
- django_cfg/apps/newsletter/admin/newsletter_admin.py +531 -193
- django_cfg/apps/payments/admin/__init__.py +18 -27
- django_cfg/apps/payments/admin/api_keys_admin.py +179 -546
- django_cfg/apps/payments/admin/balance_admin.py +166 -632
- django_cfg/apps/payments/admin/currencies_admin.py +235 -607
- django_cfg/apps/payments/admin/endpoint_groups_admin.py +127 -0
- django_cfg/apps/payments/admin/filters.py +83 -3
- django_cfg/apps/payments/admin/networks_admin.py +269 -0
- django_cfg/apps/payments/admin/payments_admin.py +183 -460
- django_cfg/apps/payments/admin/subscriptions_admin.py +119 -636
- django_cfg/apps/payments/admin/tariffs_admin.py +248 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +153 -34
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +43 -17
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +109 -63
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/management/commands/manage_currencies.py +236 -274
- django_cfg/apps/payments/management/commands/manage_providers.py +4 -1
- django_cfg/apps/payments/middleware/api_access.py +32 -6
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/balance.py +12 -0
- django_cfg/apps/payments/models/currencies.py +106 -32
- django_cfg/apps/payments/models/managers/currency_managers.py +65 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +266 -39
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +303 -41
- django_cfg/apps/payments/services/providers/models/__init__.py +42 -0
- django_cfg/apps/payments/services/providers/models/base.py +145 -0
- django_cfg/apps/payments/services/providers/models/providers.py +87 -0
- django_cfg/apps/payments/services/providers/models/universal.py +48 -0
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +31 -0
- django_cfg/apps/payments/services/providers/nowpayments/config.py +70 -0
- django_cfg/apps/payments/services/providers/nowpayments/models.py +150 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +879 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +557 -0
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +9 -37
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +29 -6
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +98 -32
- django_cfg/apps/payments/tasks/__init__.py +39 -0
- django_cfg/apps/payments/tasks/types.py +73 -0
- django_cfg/apps/payments/tasks/usage_tracking.py +308 -0
- django_cfg/apps/payments/templates/admin/payments/_components/dashboard_header.html +23 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_card.html +25 -0
- django_cfg/apps/payments/templates/admin/payments/_components/stats_grid.html +16 -0
- django_cfg/apps/payments/templates/admin/payments/apikey/change_list.html +39 -0
- django_cfg/apps/payments/templates/admin/payments/balance/change_list.html +50 -0
- django_cfg/apps/payments/templates/admin/payments/currency/change_list.html +40 -0
- django_cfg/apps/payments/templates/admin/payments/payment/change_list.html +48 -0
- django_cfg/apps/payments/templates/admin/payments/subscription/change_list.html +48 -0
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +8 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +22 -8
- django_cfg/apps/support/admin/__init__.py +10 -1
- django_cfg/apps/support/admin/support_admin.py +338 -141
- django_cfg/apps/tasks/admin/__init__.py +11 -0
- django_cfg/apps/tasks/admin/tasks_admin.py +430 -0
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/migrate_all.py +9 -3
- django_cfg/management/commands/migrator.py +11 -6
- django_cfg/management/commands/rundramatiq.py +3 -2
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_admin/__init__.py +64 -0
- django_cfg/modules/django_admin/decorators/__init__.py +13 -0
- django_cfg/modules/django_admin/decorators/actions.py +106 -0
- django_cfg/modules/django_admin/decorators/display.py +106 -0
- django_cfg/modules/django_admin/mixins/__init__.py +14 -0
- django_cfg/modules/django_admin/mixins/display_mixin.py +81 -0
- django_cfg/modules/django_admin/mixins/optimization_mixin.py +41 -0
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +202 -0
- django_cfg/modules/django_admin/models/__init__.py +20 -0
- django_cfg/modules/django_admin/models/action_models.py +33 -0
- django_cfg/modules/django_admin/models/badge_models.py +20 -0
- django_cfg/modules/django_admin/models/base.py +26 -0
- django_cfg/modules/django_admin/models/display_models.py +31 -0
- django_cfg/modules/django_admin/utils/badges.py +159 -0
- django_cfg/modules/django_admin/utils/displays.py +247 -0
- django_cfg/modules/django_currency/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/__init__.py +2 -2
- django_cfg/modules/django_currency/clients/hybrid_client.py +587 -0
- django_cfg/modules/django_currency/core/converter.py +12 -12
- django_cfg/modules/django_currency/database/__init__.py +2 -2
- django_cfg/modules/django_currency/database/database_loader.py +93 -42
- django_cfg/modules/django_llm/llm/client.py +10 -2
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/modules/django_unfold/callbacks/actions.py +1 -1
- django_cfg/modules/django_unfold/callbacks/statistics.py +1 -1
- django_cfg/modules/django_unfold/dashboard.py +14 -13
- django_cfg/modules/django_unfold/models/config.py +1 -1
- django_cfg/registry/core.py +7 -9
- django_cfg/registry/third_party.py +2 -2
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/RECORD +198 -160
- django_cfg/apps/accounts/admin/activity.py +0 -96
- django_cfg/apps/accounts/admin/group.py +0 -17
- django_cfg/apps/accounts/admin/otp.py +0 -59
- django_cfg/apps/accounts/admin/registration_source.py +0 -97
- django_cfg/apps/accounts/admin/twilio_response.py +0 -227
- django_cfg/apps/accounts/admin/user.py +0 -300
- django_cfg/apps/agents/core/agent.py +0 -281
- django_cfg/apps/payments/admin_interface/old/payments/base.html +0 -175
- django_cfg/apps/payments/admin_interface/old/payments/components/dev_tool_card.html +0 -125
- django_cfg/apps/payments/admin_interface/old/payments/components/loading_spinner.html +0 -16
- django_cfg/apps/payments/admin_interface/old/payments/components/ngrok_status_card.html +0 -113
- django_cfg/apps/payments/admin_interface/old/payments/components/notification.html +0 -27
- django_cfg/apps/payments/admin_interface/old/payments/components/provider_card.html +0 -86
- django_cfg/apps/payments/admin_interface/old/payments/components/status_card.html +0 -35
- django_cfg/apps/payments/admin_interface/old/payments/currency_converter.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_dashboard.html +0 -309
- django_cfg/apps/payments/admin_interface/old/payments/payment_form.html +0 -303
- django_cfg/apps/payments/admin_interface/old/payments/payment_list.html +0 -382
- django_cfg/apps/payments/admin_interface/old/payments/payment_status.html +0 -500
- django_cfg/apps/payments/admin_interface/old/payments/webhook_dashboard.html +0 -518
- django_cfg/apps/payments/admin_interface/old/static/payments/css/components.css +0 -619
- django_cfg/apps/payments/admin_interface/old/static/payments/css/dashboard.css +0 -188
- django_cfg/apps/payments/admin_interface/old/static/payments/js/components.js +0 -545
- django_cfg/apps/payments/admin_interface/old/static/payments/js/ngrok-status.js +0 -163
- django_cfg/apps/payments/admin_interface/old/static/payments/js/utils.js +0 -412
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/services/providers/nowpayments.py +0 -478
- django_cfg/apps/tasks/admin.py +0 -320
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/middleware/static_nocache.py +0 -55
- django_cfg/modules/django_currency/clients/yahoo_client.py +0 -157
- /django_cfg/modules/{django_unfold → django_admin}/icons/README.md +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/__init__.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/constants.py +0 -0
- /django_cfg/modules/{django_unfold → django_admin}/icons/generate_icons.py +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
@@ -10,8 +10,22 @@ class PaymentAPIClient {
|
|
10
10
|
|
11
11
|
// Helper method to get CSRF token
|
12
12
|
getCSRFToken() {
|
13
|
-
|
14
|
-
|
13
|
+
// Try to get token from form input first
|
14
|
+
const tokenInput = document.querySelector('[name=csrfmiddlewaretoken]');
|
15
|
+
if (tokenInput && tokenInput.value) {
|
16
|
+
return tokenInput.value;
|
17
|
+
}
|
18
|
+
|
19
|
+
// Fallback to cookie
|
20
|
+
const cookieString = document.cookie;
|
21
|
+
if (cookieString.includes('csrftoken=')) {
|
22
|
+
const match = cookieString.match(/csrftoken=([^;]+)/);
|
23
|
+
if (match) {
|
24
|
+
return match[1];
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
return '';
|
15
29
|
}
|
16
30
|
|
17
31
|
// Generic request method
|
@@ -129,16 +143,20 @@ class PaymentAPIClient {
|
|
129
143
|
supported: (params = {}) => this.get(`${this.baseURL}/currencies/supported/`, params),
|
130
144
|
|
131
145
|
// Get currencies by provider
|
132
|
-
byProvider: (provider) => this.get(`${this.baseURL}/currencies
|
146
|
+
byProvider: (provider) => this.get(`${this.baseURL}/provider-currencies/`, { provider, page_size: 1000 }),
|
133
147
|
|
134
148
|
// Get exchange rates
|
135
149
|
rates: (params = {}) => this.get(`${this.baseURL}/currencies/rates/`, params),
|
136
150
|
|
137
151
|
// Convert currency
|
138
|
-
convert: (from, to, amount) => this.post(`${this.baseURL}/currencies/convert/`, {
|
152
|
+
convert: (from, to, amount) => this.post(`${this.baseURL}/currencies/convert/`, {
|
153
|
+
from_currency: from,
|
154
|
+
to_currency: to,
|
155
|
+
amount: amount
|
156
|
+
}),
|
139
157
|
|
140
158
|
// Get provider-specific currency configurations
|
141
|
-
providerConfigs: (provider) => this.get(`${this.baseURL}/provider-currencies/`, { provider }),
|
159
|
+
providerConfigs: (provider) => this.get(`${this.baseURL}/provider-currencies/`, { provider, page_size: 1000 }),
|
142
160
|
|
143
161
|
// Get currencies grouped by provider
|
144
162
|
byProviderGrouped: () => this.get(`${this.baseURL}/provider-currencies/by_provider/`)
|
@@ -283,6 +301,7 @@ class PaymentAPIClient {
|
|
283
301
|
delete: (id) => this.delete(`${this.adminURL}/api/payments/${id}/`),
|
284
302
|
cancel: (id) => this.post(`${this.adminURL}/api/payments/${id}/cancel/`),
|
285
303
|
refund: (id) => this.post(`${this.adminURL}/api/payments/${id}/refund/`),
|
304
|
+
refreshStatus: (id) => this.post(`${this.adminURL}/api/payments/${id}/refresh_status/`),
|
286
305
|
stats: () => this.get(`${this.adminURL}/api/payments/stats/`)
|
287
306
|
},
|
288
307
|
|
@@ -302,7 +321,7 @@ class PaymentAPIClient {
|
|
302
321
|
|
303
322
|
// Webhook test method
|
304
323
|
webhookTest: {
|
305
|
-
send: (url, eventType) => this.post(`${this.adminURL}/api/
|
324
|
+
send: (url, eventType) => this.post(`${this.adminURL}/api/webhook-test/test/`, {
|
306
325
|
webhook_url: url,
|
307
326
|
event_type: eventType
|
308
327
|
})
|
@@ -377,7 +396,11 @@ class PaymentAPIClient {
|
|
377
396
|
}
|
378
397
|
|
379
398
|
// Create global instance
|
399
|
+
console.log('🔧 PaymentAPIClient: Creating global instance...');
|
380
400
|
window.PaymentAPI = new PaymentAPIClient();
|
401
|
+
console.log('✅ PaymentAPIClient: Global instance created');
|
402
|
+
console.log('🔍 PaymentAPI.currencies:', window.PaymentAPI.currencies);
|
403
|
+
console.log('🔍 PaymentAPI.admin:', window.PaymentAPI.admin);
|
381
404
|
|
382
405
|
// Export for modules
|
383
406
|
if (typeof module !== 'undefined' && module.exports) {
|
@@ -0,0 +1,167 @@
|
|
1
|
+
/**
|
2
|
+
* Payment Detail Page JavaScript
|
3
|
+
* Handles payment detail page functionality including AJAX status refresh
|
4
|
+
*/
|
5
|
+
|
6
|
+
function paymentDetail() {
|
7
|
+
return {
|
8
|
+
loading: false,
|
9
|
+
showQRCode: false,
|
10
|
+
paymentId: null,
|
11
|
+
|
12
|
+
init(paymentId) {
|
13
|
+
this.paymentId = paymentId;
|
14
|
+
// Show QR code by default if payment address exists
|
15
|
+
const payAddress = document.querySelector('[data-field="pay_address"]');
|
16
|
+
if (payAddress && payAddress.textContent.trim()) {
|
17
|
+
this.showQRCode = true;
|
18
|
+
}
|
19
|
+
},
|
20
|
+
|
21
|
+
async refreshPaymentStatus() {
|
22
|
+
this.loading = true;
|
23
|
+
try {
|
24
|
+
// Use the existing PaymentAPI client
|
25
|
+
const data = await PaymentAPI.admin.payments.refreshStatus(this.paymentId);
|
26
|
+
|
27
|
+
if (data.success) {
|
28
|
+
// Show success notification
|
29
|
+
PaymentAPI.utils.showNotification(data.message, 'success');
|
30
|
+
|
31
|
+
// Update payment data on page without full reload
|
32
|
+
this.updatePaymentData(data.payment);
|
33
|
+
} else {
|
34
|
+
// Show error notification
|
35
|
+
PaymentAPI.utils.showNotification(data.message || 'Failed to refresh payment status', 'error');
|
36
|
+
}
|
37
|
+
} catch (error) {
|
38
|
+
console.error('Failed to refresh payment status:', error);
|
39
|
+
PaymentAPI.utils.showNotification('Network error while refreshing status', 'error');
|
40
|
+
} finally {
|
41
|
+
this.loading = false;
|
42
|
+
}
|
43
|
+
},
|
44
|
+
|
45
|
+
updatePaymentData(paymentData) {
|
46
|
+
// Update status badge
|
47
|
+
const statusBadge = document.querySelector('.payment-status-badge');
|
48
|
+
if (statusBadge && paymentData.status) {
|
49
|
+
statusBadge.textContent = paymentData.status.toUpperCase();
|
50
|
+
statusBadge.className = `payment-status-badge inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${this.getStatusBadgeClass(paymentData.status)}`;
|
51
|
+
}
|
52
|
+
|
53
|
+
// Update other fields that might have changed
|
54
|
+
const fields = {
|
55
|
+
'pay_amount': paymentData.pay_amount,
|
56
|
+
'transaction_hash': paymentData.transaction_hash,
|
57
|
+
'pay_address': paymentData.pay_address,
|
58
|
+
'payment_url': paymentData.payment_url,
|
59
|
+
'expires_at': paymentData.expires_at,
|
60
|
+
'confirmed_at': paymentData.confirmed_at,
|
61
|
+
'updated_at': paymentData.updated_at,
|
62
|
+
'status_changed_at': paymentData.status_changed_at
|
63
|
+
};
|
64
|
+
|
65
|
+
Object.entries(fields).forEach(([field, value]) => {
|
66
|
+
const element = document.querySelector(`[data-field="${field}"]`);
|
67
|
+
if (element && value !== null && value !== undefined) {
|
68
|
+
if (field.includes('_at') && value) {
|
69
|
+
// Format datetime fields
|
70
|
+
element.textContent = new Date(value).toLocaleString();
|
71
|
+
} else if (field === 'pay_amount' && paymentData.currency) {
|
72
|
+
// Format pay_amount with currency
|
73
|
+
element.textContent = `${parseFloat(value).toFixed(8)} ${paymentData.currency.code}`;
|
74
|
+
} else {
|
75
|
+
element.textContent = value;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
});
|
79
|
+
|
80
|
+
// Update error info if present
|
81
|
+
if (paymentData.error_info) {
|
82
|
+
const errorElement = document.querySelector('[data-field="error_info"]');
|
83
|
+
if (errorElement) {
|
84
|
+
errorElement.textContent = JSON.stringify(paymentData.error_info, null, 2);
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
// Update QR code visibility if pay_address changed
|
89
|
+
if (paymentData.pay_address && !this.showQRCode) {
|
90
|
+
this.showQRCode = true;
|
91
|
+
}
|
92
|
+
},
|
93
|
+
|
94
|
+
getStatusBadgeClass(status) {
|
95
|
+
const statusClasses = {
|
96
|
+
'pending': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-300',
|
97
|
+
'confirming': 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300',
|
98
|
+
'completed': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300',
|
99
|
+
'confirmed': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300',
|
100
|
+
'failed': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300',
|
101
|
+
'cancelled': 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300',
|
102
|
+
'expired': 'bg-orange-100 text-orange-800 dark:bg-orange-900 dark:text-orange-300',
|
103
|
+
'refunded': 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300'
|
104
|
+
};
|
105
|
+
return statusClasses[status] || 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-300';
|
106
|
+
},
|
107
|
+
|
108
|
+
async cancelPayment() {
|
109
|
+
if (!confirm('Are you sure you want to cancel this payment?')) {
|
110
|
+
return;
|
111
|
+
}
|
112
|
+
|
113
|
+
this.loading = true;
|
114
|
+
try {
|
115
|
+
const data = await PaymentAPI.admin.payments.cancel(this.paymentId);
|
116
|
+
PaymentAPI.utils.showNotification('Payment cancelled successfully', 'success');
|
117
|
+
|
118
|
+
// Update payment data
|
119
|
+
this.updatePaymentData(data);
|
120
|
+
} catch (error) {
|
121
|
+
console.error('Failed to cancel payment:', error);
|
122
|
+
PaymentAPI.utils.showNotification('Failed to cancel payment', 'error');
|
123
|
+
} finally {
|
124
|
+
this.loading = false;
|
125
|
+
}
|
126
|
+
},
|
127
|
+
|
128
|
+
exportDetails() {
|
129
|
+
// Get payment data from the page
|
130
|
+
const paymentData = {
|
131
|
+
id: this.paymentId,
|
132
|
+
status: document.querySelector('.payment-status-badge')?.textContent || 'Unknown',
|
133
|
+
amount_usd: document.querySelector('[data-field="amount_usd"]')?.textContent || '',
|
134
|
+
pay_amount: document.querySelector('[data-field="pay_amount"]')?.textContent || '',
|
135
|
+
currency: document.querySelector('[data-field="currency"]')?.textContent || '',
|
136
|
+
provider: document.querySelector('[data-field="provider"]')?.textContent || '',
|
137
|
+
pay_address: document.querySelector('[data-field="pay_address"]')?.textContent || '',
|
138
|
+
transaction_hash: document.querySelector('[data-field="transaction_hash"]')?.textContent || '',
|
139
|
+
created_at: document.querySelector('[data-field="created_at"]')?.textContent || '',
|
140
|
+
updated_at: document.querySelector('[data-field="updated_at"]')?.textContent || ''
|
141
|
+
};
|
142
|
+
|
143
|
+
// Create CSV content
|
144
|
+
const csvContent = Object.entries(paymentData)
|
145
|
+
.map(([key, value]) => `${key},${value}`)
|
146
|
+
.join('\n');
|
147
|
+
|
148
|
+
// Download CSV
|
149
|
+
const blob = new Blob([csvContent], { type: 'text/csv' });
|
150
|
+
const url = window.URL.createObjectURL(blob);
|
151
|
+
const a = document.createElement('a');
|
152
|
+
a.href = url;
|
153
|
+
a.download = `payment_${this.paymentId}_details.csv`;
|
154
|
+
document.body.appendChild(a);
|
155
|
+
a.click();
|
156
|
+
document.body.removeChild(a);
|
157
|
+
window.URL.revokeObjectURL(url);
|
158
|
+
}
|
159
|
+
};
|
160
|
+
}
|
161
|
+
|
162
|
+
// Auto-initialize if PaymentAPI is available
|
163
|
+
document.addEventListener('DOMContentLoaded', function() {
|
164
|
+
if (typeof PaymentAPI === 'undefined') {
|
165
|
+
console.warn('PaymentAPI not found. Make sure api-client.js is loaded before payment-detail.js');
|
166
|
+
}
|
167
|
+
});
|
@@ -22,12 +22,18 @@ function paymentForm() {
|
|
22
22
|
],
|
23
23
|
conversionResult: null,
|
24
24
|
users: [],
|
25
|
+
errorMessage: '',
|
25
26
|
|
26
27
|
async init() {
|
28
|
+
console.log('🚀 PaymentForm: Initializing...');
|
29
|
+
console.log('🔍 PaymentAPI object:', window.PaymentAPI);
|
30
|
+
console.log('🔍 PaymentAPI.currencies:', window.PaymentAPI?.currencies);
|
31
|
+
console.log('🔍 PaymentAPI.admin:', window.PaymentAPI?.admin);
|
27
32
|
await this.loadInitialData();
|
28
33
|
},
|
29
34
|
|
30
35
|
async loadInitialData() {
|
36
|
+
console.log('📊 PaymentForm: Loading initial data...');
|
31
37
|
this.loading = true;
|
32
38
|
try {
|
33
39
|
// Load all currencies and provider-specific currencies
|
@@ -36,8 +42,9 @@ function paymentForm() {
|
|
36
42
|
this.loadProviderCurrencies(),
|
37
43
|
this.loadUsers()
|
38
44
|
]);
|
45
|
+
console.log('✅ PaymentForm: Initial data loaded successfully');
|
39
46
|
} catch (error) {
|
40
|
-
console.error('Failed to load initial data:', error);
|
47
|
+
console.error('❌ PaymentForm: Failed to load initial data:', error);
|
41
48
|
PaymentAPI.utils.showNotification('Failed to load form data', 'error');
|
42
49
|
} finally {
|
43
50
|
this.loading = false;
|
@@ -45,44 +52,60 @@ function paymentForm() {
|
|
45
52
|
},
|
46
53
|
|
47
54
|
async loadAllCurrencies() {
|
55
|
+
console.log('💰 PaymentForm: Loading all currencies...');
|
56
|
+
console.log('🔍 PaymentAPI.currencies.supported type:', typeof PaymentAPI.currencies.supported);
|
48
57
|
try {
|
58
|
+
if (typeof PaymentAPI.currencies.supported !== 'function') {
|
59
|
+
console.error('❌ PaymentAPI.currencies.supported is not a function:', PaymentAPI.currencies.supported);
|
60
|
+
console.log('🔍 Available currencies methods:', Object.keys(PaymentAPI.currencies));
|
61
|
+
return;
|
62
|
+
}
|
49
63
|
const data = await PaymentAPI.currencies.supported();
|
50
|
-
|
64
|
+
console.log('📊 Currencies API response:', data);
|
65
|
+
this.allCurrencies = data.currencies?.currencies || data.currencies || [];
|
66
|
+
console.log('✅ Loaded currencies:', this.allCurrencies.length);
|
51
67
|
} catch (error) {
|
52
|
-
console.error('Failed to load currencies:', error);
|
68
|
+
console.error('❌ Failed to load currencies:', error);
|
53
69
|
}
|
54
70
|
},
|
55
71
|
|
56
72
|
async loadProviderCurrencies() {
|
57
|
-
if (!this.form.provider)
|
73
|
+
if (!this.form.provider) {
|
74
|
+
console.log('⚠️ PaymentForm: No provider selected, skipping currency load');
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
|
78
|
+
console.log('🏦 PaymentForm: Loading provider currencies for:', this.form.provider);
|
79
|
+
console.log('🔍 PaymentAPI.currencies.byProvider type:', typeof PaymentAPI.currencies.byProvider);
|
58
80
|
|
59
81
|
this.loadingCurrencies = true;
|
60
82
|
try {
|
61
|
-
|
62
|
-
|
83
|
+
if (typeof PaymentAPI.currencies.byProvider !== 'function') {
|
84
|
+
console.error('❌ PaymentAPI.currencies.byProvider is not a function:', PaymentAPI.currencies.byProvider);
|
85
|
+
console.log('🔍 Available currencies methods:', Object.keys(PaymentAPI.currencies));
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
|
89
|
+
// Use the provider-currencies API endpoint directly
|
90
|
+
const response = await PaymentAPI.request(`/api/payments/provider-currencies/by_provider/?provider=${this.form.provider}`);
|
91
|
+
console.log('📊 Provider currencies API response:', response);
|
63
92
|
|
64
|
-
//
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
network_name: pc.network?.name || null,
|
72
|
-
min_amount: pc.min_amount,
|
73
|
-
max_amount: pc.max_amount,
|
74
|
-
fee_percentage: pc.fee_percentage,
|
75
|
-
fixed_fee: pc.fixed_fee,
|
76
|
-
provider_code: pc.provider_currency_code
|
77
|
-
}));
|
93
|
+
// Extract currencies for the specific provider
|
94
|
+
const providerData = response.currencies_by_provider?.[this.form.provider];
|
95
|
+
this.currencies = providerData?.currencies || [];
|
96
|
+
|
97
|
+
console.log('🔍 Sample currency structure:', this.currencies[0]);
|
98
|
+
|
99
|
+
console.log('✅ Loaded provider currencies:', this.currencies.length);
|
78
100
|
|
79
101
|
// If current currency is not supported by provider, reset it
|
80
|
-
if (this.form.currency_code && !this.currencies.find(c => c.
|
102
|
+
if (this.form.currency_code && !this.currencies.find(c => c.provider_currency_code === this.form.currency_code)) {
|
103
|
+
console.log('⚠️ Current currency not supported by provider, resetting');
|
81
104
|
this.form.currency_code = '';
|
82
105
|
this.conversionResult = null;
|
83
106
|
}
|
84
107
|
} catch (error) {
|
85
|
-
console.error('Failed to load provider currencies:', error);
|
108
|
+
console.error('❌ Failed to load provider currencies:', error);
|
86
109
|
this.currencies = [];
|
87
110
|
} finally {
|
88
111
|
this.loadingCurrencies = false;
|
@@ -90,17 +113,43 @@ function paymentForm() {
|
|
90
113
|
},
|
91
114
|
|
92
115
|
async loadUsers() {
|
116
|
+
console.log('👥 PaymentForm: Loading users...');
|
117
|
+
console.log('🔍 PaymentAPI.admin:', PaymentAPI.admin);
|
118
|
+
console.log('🔍 PaymentAPI.admin?.users:', PaymentAPI.admin?.users);
|
119
|
+
console.log('🔍 PaymentAPI.admin?.users?.list type:', typeof PaymentAPI.admin?.users?.list);
|
120
|
+
|
93
121
|
try {
|
122
|
+
if (!PaymentAPI.admin) {
|
123
|
+
console.error('❌ PaymentAPI.admin is undefined');
|
124
|
+
this.users = [{ id: '', username: 'Select User', email: '' }];
|
125
|
+
return;
|
126
|
+
}
|
127
|
+
|
128
|
+
if (!PaymentAPI.admin.users) {
|
129
|
+
console.error('❌ PaymentAPI.admin.users is undefined');
|
130
|
+
this.users = [{ id: '', username: 'Select User', email: '' }];
|
131
|
+
return;
|
132
|
+
}
|
133
|
+
|
134
|
+
if (typeof PaymentAPI.admin.users.list !== 'function') {
|
135
|
+
console.error('❌ PaymentAPI.admin.users.list is not a function:', PaymentAPI.admin.users.list);
|
136
|
+
this.users = [{ id: '', username: 'Select User', email: '' }];
|
137
|
+
return;
|
138
|
+
}
|
139
|
+
|
94
140
|
const data = await PaymentAPI.admin.users.list();
|
141
|
+
console.log('📊 Users API response:', data);
|
95
142
|
this.users = data.results || data || [];
|
96
143
|
|
97
144
|
// If no users loaded, try to get current user info
|
98
145
|
if (this.users.length === 0) {
|
99
|
-
console.warn('No users loaded from admin API');
|
146
|
+
console.warn('⚠️ No users loaded from admin API');
|
100
147
|
this.users = [{ id: '', username: 'Select User', email: '' }];
|
148
|
+
} else {
|
149
|
+
console.log('✅ Loaded users:', this.users.length);
|
101
150
|
}
|
102
151
|
} catch (error) {
|
103
|
-
console.error('Failed to load users:', error);
|
152
|
+
console.error('❌ Failed to load users:', error);
|
104
153
|
// Set empty option for user selection
|
105
154
|
this.users = [{ id: '', username: 'Select User', email: '' }];
|
106
155
|
}
|
@@ -122,11 +171,15 @@ function paymentForm() {
|
|
122
171
|
if (!this.form.amount_usd || !this.form.currency_code) return;
|
123
172
|
|
124
173
|
try {
|
125
|
-
|
174
|
+
// Get the original currency code for conversion (not provider code)
|
175
|
+
const currencyInfo = this.getCurrencyInfo(this.form.currency_code);
|
176
|
+
const originalCurrencyCode = currencyInfo.currency?.code || this.form.currency_code;
|
177
|
+
|
178
|
+
const result = await PaymentAPI.currencies.convert('USD', originalCurrencyCode, this.form.amount_usd);
|
126
179
|
this.conversionResult = {
|
127
180
|
amount: result.converted_amount,
|
128
181
|
rate: result.rate,
|
129
|
-
currency:
|
182
|
+
currency: originalCurrencyCode
|
130
183
|
};
|
131
184
|
} catch (error) {
|
132
185
|
console.error('Currency conversion failed:', error);
|
@@ -134,10 +187,10 @@ function paymentForm() {
|
|
134
187
|
}
|
135
188
|
},
|
136
189
|
|
137
|
-
getCurrencyInfo(
|
138
|
-
return this.currencies.find(c => c.
|
139
|
-
this.allCurrencies.find(c => c.code ===
|
140
|
-
{ code, name:
|
190
|
+
getCurrencyInfo(providerCurrencyCode) {
|
191
|
+
return this.currencies.find(c => c.provider_currency_code === providerCurrencyCode) ||
|
192
|
+
this.allCurrencies.find(c => c.code === providerCurrencyCode) ||
|
193
|
+
{ provider_currency_code: providerCurrencyCode, currency: { code: providerCurrencyCode, name: providerCurrencyCode, currency_type: 'unknown' } };
|
141
194
|
},
|
142
195
|
|
143
196
|
validateForm() {
|
@@ -151,10 +204,18 @@ function paymentForm() {
|
|
151
204
|
return errors;
|
152
205
|
},
|
153
206
|
|
207
|
+
clearError() {
|
208
|
+
this.errorMessage = '';
|
209
|
+
},
|
210
|
+
|
154
211
|
async submitForm() {
|
212
|
+
// Clear previous error
|
213
|
+
this.clearError();
|
214
|
+
|
155
215
|
const errors = this.validateForm();
|
156
216
|
if (errors.length > 0) {
|
157
|
-
|
217
|
+
this.errorMessage = errors.join(', ');
|
218
|
+
PaymentAPI.utils.showNotification('Please fix form errors', 'error');
|
158
219
|
return;
|
159
220
|
}
|
160
221
|
|
@@ -166,7 +227,12 @@ function paymentForm() {
|
|
166
227
|
window.location.href = `/cfg/admin/django_cfg_payments/admin/payments/${data.id}/`;
|
167
228
|
} catch (error) {
|
168
229
|
console.error('Error:', error);
|
169
|
-
|
230
|
+
|
231
|
+
// Show detailed error in form
|
232
|
+
this.errorMessage = error.message || 'Failed to create payment';
|
233
|
+
|
234
|
+
// Show brief notification
|
235
|
+
PaymentAPI.utils.showNotification('Payment creation failed', 'error');
|
170
236
|
} finally {
|
171
237
|
this.loading = false;
|
172
238
|
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
"""
|
2
|
+
Payments Background Tasks
|
3
|
+
|
4
|
+
Dramatiq tasks for usage tracking, statistics, and payment processing.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from .usage_tracking import (
|
8
|
+
update_api_key_usage_async,
|
9
|
+
update_subscription_usage_async,
|
10
|
+
batch_update_usage_counters,
|
11
|
+
cleanup_stale_usage_cache
|
12
|
+
)
|
13
|
+
|
14
|
+
from .types import (
|
15
|
+
TaskResult,
|
16
|
+
UsageUpdateRequest,
|
17
|
+
UsageUpdateResult,
|
18
|
+
BatchUpdateRequest,
|
19
|
+
BatchUpdateResult,
|
20
|
+
CleanupResult,
|
21
|
+
CacheStats
|
22
|
+
)
|
23
|
+
|
24
|
+
__all__ = [
|
25
|
+
# Usage tracking tasks
|
26
|
+
'update_api_key_usage_async',
|
27
|
+
'update_subscription_usage_async',
|
28
|
+
'batch_update_usage_counters',
|
29
|
+
'cleanup_stale_usage_cache',
|
30
|
+
|
31
|
+
# Pydantic types
|
32
|
+
'TaskResult',
|
33
|
+
'UsageUpdateRequest',
|
34
|
+
'UsageUpdateResult',
|
35
|
+
'BatchUpdateRequest',
|
36
|
+
'BatchUpdateResult',
|
37
|
+
'CleanupResult',
|
38
|
+
'CacheStats',
|
39
|
+
]
|
@@ -0,0 +1,73 @@
|
|
1
|
+
"""
|
2
|
+
Pydantic types for background tasks.
|
3
|
+
"""
|
4
|
+
|
5
|
+
from typing import Optional, Dict, Any, List
|
6
|
+
from pydantic import BaseModel, Field, ConfigDict
|
7
|
+
from datetime import datetime
|
8
|
+
|
9
|
+
|
10
|
+
class TaskResult(BaseModel):
|
11
|
+
"""Base result type for all background tasks."""
|
12
|
+
model_config = ConfigDict(validate_assignment=True)
|
13
|
+
|
14
|
+
status: str = Field(description="Task execution status")
|
15
|
+
message: Optional[str] = Field(None, description="Human-readable message")
|
16
|
+
error: Optional[str] = Field(None, description="Error message if failed")
|
17
|
+
processing_time_ms: Optional[float] = Field(None, description="Processing time in milliseconds")
|
18
|
+
timestamp: datetime = Field(default_factory=datetime.utcnow, description="Task completion timestamp")
|
19
|
+
|
20
|
+
|
21
|
+
class UsageUpdateRequest(BaseModel):
|
22
|
+
"""Request for updating usage counters."""
|
23
|
+
model_config = ConfigDict(validate_assignment=True)
|
24
|
+
|
25
|
+
resource_id: str = Field(description="Resource ID (API key or subscription)")
|
26
|
+
increment: int = Field(default=1, description="Amount to increment")
|
27
|
+
ip_address: Optional[str] = Field(None, description="Client IP address")
|
28
|
+
metadata: Dict[str, Any] = Field(default_factory=dict, description="Additional metadata")
|
29
|
+
|
30
|
+
|
31
|
+
class UsageUpdateResult(TaskResult):
|
32
|
+
"""Result of usage update operation."""
|
33
|
+
|
34
|
+
resource_id: str = Field(description="Updated resource ID")
|
35
|
+
total_requests: Optional[int] = Field(None, description="Total requests after update")
|
36
|
+
increment: int = Field(description="Amount incremented")
|
37
|
+
user_id: Optional[int] = Field(None, description="Associated user ID")
|
38
|
+
|
39
|
+
|
40
|
+
class BatchUpdateRequest(BaseModel):
|
41
|
+
"""Request for batch usage updates."""
|
42
|
+
model_config = ConfigDict(validate_assignment=True)
|
43
|
+
|
44
|
+
api_key_updates: List[UsageUpdateRequest] = Field(default_factory=list, description="API key updates")
|
45
|
+
subscription_updates: List[UsageUpdateRequest] = Field(default_factory=list, description="Subscription updates")
|
46
|
+
force_flush: bool = Field(default=False, description="Force immediate processing")
|
47
|
+
|
48
|
+
|
49
|
+
class BatchUpdateResult(TaskResult):
|
50
|
+
"""Result of batch update operation."""
|
51
|
+
|
52
|
+
api_keys_updated: int = Field(default=0, description="Number of API keys updated")
|
53
|
+
subscriptions_updated: int = Field(default=0, description="Number of subscriptions updated")
|
54
|
+
errors: List[Dict[str, Any]] = Field(default_factory=list, description="Processing errors")
|
55
|
+
total_items: int = Field(description="Total items processed")
|
56
|
+
|
57
|
+
|
58
|
+
class CleanupResult(TaskResult):
|
59
|
+
"""Result of cleanup operation."""
|
60
|
+
|
61
|
+
cleaned_entries: int = Field(default=0, description="Number of entries cleaned")
|
62
|
+
cleanup_type: str = Field(description="Type of cleanup performed")
|
63
|
+
cutoff_date: Optional[datetime] = Field(None, description="Cleanup cutoff date")
|
64
|
+
|
65
|
+
|
66
|
+
class CacheStats(BaseModel):
|
67
|
+
"""Cache statistics."""
|
68
|
+
model_config = ConfigDict(validate_assignment=True)
|
69
|
+
|
70
|
+
total_keys: int = Field(description="Total cache keys")
|
71
|
+
expired_keys: int = Field(description="Expired cache keys")
|
72
|
+
memory_usage_mb: Optional[float] = Field(None, description="Memory usage in MB")
|
73
|
+
hit_rate: Optional[float] = Field(None, description="Cache hit rate percentage")
|