django-cfg 1.3.7__py3-none-any.whl → 1.3.9__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 +258 -0
- django_cfg/apps/payments/admin/payments_admin.py +171 -461
- 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 +105 -34
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +12 -16
- django_cfg/apps/payments/admin_interface/views/__init__.py +2 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +13 -18
- 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/0002_currency_usd_rate_currency_usd_rate_updated_at.py +26 -0
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +28 -0
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +30 -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/services/core/currency_service.py +35 -28
- django_cfg/apps/payments/services/core/payment_service.py +1 -1
- django_cfg/apps/payments/services/providers/__init__.py +3 -0
- django_cfg/apps/payments/services/providers/base.py +95 -39
- django_cfg/apps/payments/services/providers/models/__init__.py +40 -0
- django_cfg/apps/payments/services/providers/models/base.py +122 -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.py → nowpayments/provider.py} +240 -209
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +196 -0
- django_cfg/apps/payments/services/providers/registry.py +4 -32
- django_cfg/apps/payments/services/providers/sync_service.py +277 -0
- django_cfg/apps/payments/static/payments/js/api-client.js +23 -5
- django_cfg/apps/payments/static/payments/js/payment-form.js +65 -8
- 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/urls_admin.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +5 -5
- django_cfg/apps/payments/views/overview/services.py +2 -2
- django_cfg/apps/payments/views/serializers/currencies.py +4 -3
- 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/config.py +1 -1
- django_cfg/core/config.py +10 -5
- django_cfg/core/generation.py +1 -1
- django_cfg/management/commands/__init__.py +13 -1
- django_cfg/management/commands/app_agent_diagnose.py +470 -0
- django_cfg/management/commands/app_agent_generate.py +342 -0
- django_cfg/management/commands/app_agent_info.py +308 -0
- 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/middleware/__init__.py +0 -2
- django_cfg/models/api_keys.py +115 -0
- 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_app_agent/__init__.py +87 -0
- django_cfg/modules/django_app_agent/agents/__init__.py +40 -0
- django_cfg/modules/django_app_agent/agents/base/__init__.py +24 -0
- django_cfg/modules/django_app_agent/agents/base/agent.py +354 -0
- django_cfg/modules/django_app_agent/agents/base/context.py +236 -0
- django_cfg/modules/django_app_agent/agents/base/executor.py +430 -0
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +12 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +15 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +147 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +99 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +32 -0
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +290 -0
- django_cfg/modules/django_app_agent/agents/interfaces.py +376 -0
- django_cfg/modules/django_app_agent/core/__init__.py +33 -0
- django_cfg/modules/django_app_agent/core/config.py +300 -0
- django_cfg/modules/django_app_agent/core/exceptions.py +359 -0
- django_cfg/modules/django_app_agent/models/__init__.py +71 -0
- django_cfg/modules/django_app_agent/models/base.py +283 -0
- django_cfg/modules/django_app_agent/models/context.py +496 -0
- django_cfg/modules/django_app_agent/models/enums.py +481 -0
- django_cfg/modules/django_app_agent/models/requests.py +500 -0
- django_cfg/modules/django_app_agent/models/responses.py +585 -0
- django_cfg/modules/django_app_agent/pytest.ini +6 -0
- django_cfg/modules/django_app_agent/services/__init__.py +42 -0
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +30 -0
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +133 -0
- django_cfg/modules/django_app_agent/services/app_generator/context.py +40 -0
- django_cfg/modules/django_app_agent/services/app_generator/main.py +202 -0
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +316 -0
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +125 -0
- django_cfg/modules/django_app_agent/services/base.py +437 -0
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +34 -0
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +141 -0
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +276 -0
- django_cfg/modules/django_app_agent/services/context_builder/main.py +272 -0
- django_cfg/modules/django_app_agent/services/context_builder/models.py +40 -0
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +85 -0
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +31 -0
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +311 -0
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +221 -0
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +59 -0
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +94 -0
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +28 -0
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +273 -0
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +111 -0
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +251 -0
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +347 -0
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +356 -0
- django_cfg/modules/django_app_agent/services/report_service.py +332 -0
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +18 -0
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +236 -0
- django_cfg/modules/django_app_agent/services/template_manager/main.py +159 -0
- django_cfg/modules/django_app_agent/services/template_manager/models.py +36 -0
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +100 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +105 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +31 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +44 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +81 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +107 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +139 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +91 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +195 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +35 -0
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +211 -0
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +200 -0
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +25 -0
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +333 -0
- django_cfg/modules/django_app_agent/services/validation_service/main.py +242 -0
- django_cfg/modules/django_app_agent/services/validation_service/models.py +66 -0
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +352 -0
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +272 -0
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +203 -0
- django_cfg/modules/django_app_agent/ui/__init__.py +25 -0
- django_cfg/modules/django_app_agent/ui/cli.py +419 -0
- django_cfg/modules/django_app_agent/ui/rich_components.py +622 -0
- django_cfg/modules/django_app_agent/utils/__init__.py +38 -0
- django_cfg/modules/django_app_agent/utils/logging.py +360 -0
- django_cfg/modules/django_app_agent/utils/validation.py +417 -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_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 +3 -0
- 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.9.dist-info}/METADATA +2 -1
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/RECORD +223 -117
- 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/tasks/admin.py +0 -320
- 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.9.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.7.dist-info → django_cfg-1.3.9.dist-info}/licenses/LICENSE +0 -0
@@ -24,10 +24,15 @@ function paymentForm() {
|
|
24
24
|
users: [],
|
25
25
|
|
26
26
|
async init() {
|
27
|
+
console.log('🚀 PaymentForm: Initializing...');
|
28
|
+
console.log('🔍 PaymentAPI object:', window.PaymentAPI);
|
29
|
+
console.log('🔍 PaymentAPI.currencies:', window.PaymentAPI?.currencies);
|
30
|
+
console.log('🔍 PaymentAPI.admin:', window.PaymentAPI?.admin);
|
27
31
|
await this.loadInitialData();
|
28
32
|
},
|
29
33
|
|
30
34
|
async loadInitialData() {
|
35
|
+
console.log('📊 PaymentForm: Loading initial data...');
|
31
36
|
this.loading = true;
|
32
37
|
try {
|
33
38
|
// Load all currencies and provider-specific currencies
|
@@ -36,8 +41,9 @@ function paymentForm() {
|
|
36
41
|
this.loadProviderCurrencies(),
|
37
42
|
this.loadUsers()
|
38
43
|
]);
|
44
|
+
console.log('✅ PaymentForm: Initial data loaded successfully');
|
39
45
|
} catch (error) {
|
40
|
-
console.error('Failed to load initial data:', error);
|
46
|
+
console.error('❌ PaymentForm: Failed to load initial data:', error);
|
41
47
|
PaymentAPI.utils.showNotification('Failed to load form data', 'error');
|
42
48
|
} finally {
|
43
49
|
this.loading = false;
|
@@ -45,20 +51,42 @@ function paymentForm() {
|
|
45
51
|
},
|
46
52
|
|
47
53
|
async loadAllCurrencies() {
|
54
|
+
console.log('💰 PaymentForm: Loading all currencies...');
|
55
|
+
console.log('🔍 PaymentAPI.currencies.supported type:', typeof PaymentAPI.currencies.supported);
|
48
56
|
try {
|
57
|
+
if (typeof PaymentAPI.currencies.supported !== 'function') {
|
58
|
+
console.error('❌ PaymentAPI.currencies.supported is not a function:', PaymentAPI.currencies.supported);
|
59
|
+
console.log('🔍 Available currencies methods:', Object.keys(PaymentAPI.currencies));
|
60
|
+
return;
|
61
|
+
}
|
49
62
|
const data = await PaymentAPI.currencies.supported();
|
50
|
-
|
63
|
+
console.log('📊 Currencies API response:', data);
|
64
|
+
this.allCurrencies = data.currencies?.currencies || data.currencies || [];
|
65
|
+
console.log('✅ Loaded currencies:', this.allCurrencies.length);
|
51
66
|
} catch (error) {
|
52
|
-
console.error('Failed to load currencies:', error);
|
67
|
+
console.error('❌ Failed to load currencies:', error);
|
53
68
|
}
|
54
69
|
},
|
55
70
|
|
56
71
|
async loadProviderCurrencies() {
|
57
|
-
if (!this.form.provider)
|
72
|
+
if (!this.form.provider) {
|
73
|
+
console.log('⚠️ PaymentForm: No provider selected, skipping currency load');
|
74
|
+
return;
|
75
|
+
}
|
76
|
+
|
77
|
+
console.log('🏦 PaymentForm: Loading provider currencies for:', this.form.provider);
|
78
|
+
console.log('🔍 PaymentAPI.currencies.byProvider type:', typeof PaymentAPI.currencies.byProvider);
|
58
79
|
|
59
80
|
this.loadingCurrencies = true;
|
60
81
|
try {
|
61
|
-
|
82
|
+
if (typeof PaymentAPI.currencies.byProvider !== 'function') {
|
83
|
+
console.error('❌ PaymentAPI.currencies.byProvider is not a function:', PaymentAPI.currencies.byProvider);
|
84
|
+
console.log('🔍 Available currencies methods:', Object.keys(PaymentAPI.currencies));
|
85
|
+
return;
|
86
|
+
}
|
87
|
+
|
88
|
+
const data = await PaymentAPI.currencies.byProvider(this.form.provider);
|
89
|
+
console.log('📊 Provider currencies API response:', data);
|
62
90
|
this.currencies = data.results || data.currencies || [];
|
63
91
|
|
64
92
|
// Transform provider currency data for display
|
@@ -76,13 +104,16 @@ function paymentForm() {
|
|
76
104
|
provider_code: pc.provider_currency_code
|
77
105
|
}));
|
78
106
|
|
107
|
+
console.log('✅ Loaded provider currencies:', this.currencies.length);
|
108
|
+
|
79
109
|
// If current currency is not supported by provider, reset it
|
80
110
|
if (this.form.currency_code && !this.currencies.find(c => c.code === this.form.currency_code)) {
|
111
|
+
console.log('⚠️ Current currency not supported by provider, resetting');
|
81
112
|
this.form.currency_code = '';
|
82
113
|
this.conversionResult = null;
|
83
114
|
}
|
84
115
|
} catch (error) {
|
85
|
-
console.error('Failed to load provider currencies:', error);
|
116
|
+
console.error('❌ Failed to load provider currencies:', error);
|
86
117
|
this.currencies = [];
|
87
118
|
} finally {
|
88
119
|
this.loadingCurrencies = false;
|
@@ -90,17 +121,43 @@ function paymentForm() {
|
|
90
121
|
},
|
91
122
|
|
92
123
|
async loadUsers() {
|
124
|
+
console.log('👥 PaymentForm: Loading users...');
|
125
|
+
console.log('🔍 PaymentAPI.admin:', PaymentAPI.admin);
|
126
|
+
console.log('🔍 PaymentAPI.admin?.users:', PaymentAPI.admin?.users);
|
127
|
+
console.log('🔍 PaymentAPI.admin?.users?.list type:', typeof PaymentAPI.admin?.users?.list);
|
128
|
+
|
93
129
|
try {
|
130
|
+
if (!PaymentAPI.admin) {
|
131
|
+
console.error('❌ PaymentAPI.admin is undefined');
|
132
|
+
this.users = [{ id: '', username: 'Select User', email: '' }];
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
|
136
|
+
if (!PaymentAPI.admin.users) {
|
137
|
+
console.error('❌ PaymentAPI.admin.users is undefined');
|
138
|
+
this.users = [{ id: '', username: 'Select User', email: '' }];
|
139
|
+
return;
|
140
|
+
}
|
141
|
+
|
142
|
+
if (typeof PaymentAPI.admin.users.list !== 'function') {
|
143
|
+
console.error('❌ PaymentAPI.admin.users.list is not a function:', PaymentAPI.admin.users.list);
|
144
|
+
this.users = [{ id: '', username: 'Select User', email: '' }];
|
145
|
+
return;
|
146
|
+
}
|
147
|
+
|
94
148
|
const data = await PaymentAPI.admin.users.list();
|
149
|
+
console.log('📊 Users API response:', data);
|
95
150
|
this.users = data.results || data || [];
|
96
151
|
|
97
152
|
// If no users loaded, try to get current user info
|
98
153
|
if (this.users.length === 0) {
|
99
|
-
console.warn('No users loaded from admin API');
|
154
|
+
console.warn('⚠️ No users loaded from admin API');
|
100
155
|
this.users = [{ id: '', username: 'Select User', email: '' }];
|
156
|
+
} else {
|
157
|
+
console.log('✅ Loaded users:', this.users.length);
|
101
158
|
}
|
102
159
|
} catch (error) {
|
103
|
-
console.error('Failed to load users:', error);
|
160
|
+
console.error('❌ Failed to load users:', error);
|
104
161
|
// Set empty option for user selection
|
105
162
|
this.users = [{ id: '', username: 'Select User', email: '' }];
|
106
163
|
}
|
@@ -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")
|
@@ -0,0 +1,308 @@
|
|
1
|
+
"""
|
2
|
+
Background tasks for API usage tracking and statistics.
|
3
|
+
"""
|
4
|
+
import dramatiq
|
5
|
+
import logging
|
6
|
+
import time
|
7
|
+
from typing import Dict, Any, List
|
8
|
+
from django.db import transaction
|
9
|
+
from django.db.models import F
|
10
|
+
from django.utils import timezone
|
11
|
+
from django.core.cache import cache
|
12
|
+
|
13
|
+
from ..models import APIKey, Subscription
|
14
|
+
from .types import (
|
15
|
+
TaskResult,
|
16
|
+
UsageUpdateRequest,
|
17
|
+
UsageUpdateResult,
|
18
|
+
BatchUpdateResult,
|
19
|
+
CleanupResult
|
20
|
+
)
|
21
|
+
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
|
24
|
+
@dramatiq.actor(queue_name="payments")
|
25
|
+
def update_api_key_usage_async(
|
26
|
+
api_key_id: str,
|
27
|
+
ip_address: str = None,
|
28
|
+
increment: int = 1
|
29
|
+
) -> UsageUpdateResult:
|
30
|
+
"""
|
31
|
+
Update API key usage counters asynchronously.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
api_key_id: API key UUID to update
|
35
|
+
ip_address: Client IP address for logging
|
36
|
+
increment: Number to increment (default: 1)
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
Update result with statistics
|
40
|
+
"""
|
41
|
+
start_time = time.time()
|
42
|
+
|
43
|
+
try:
|
44
|
+
with transaction.atomic():
|
45
|
+
# Use F() expressions for atomic updates
|
46
|
+
updated_count = APIKey.objects.filter(id=api_key_id).update(
|
47
|
+
total_requests=F('total_requests') + increment,
|
48
|
+
last_used_at=timezone.now(),
|
49
|
+
updated_at=timezone.now()
|
50
|
+
)
|
51
|
+
|
52
|
+
if updated_count == 0:
|
53
|
+
logger.warning(f"API key not found: {api_key_id}")
|
54
|
+
return UsageUpdateResult(
|
55
|
+
status='error',
|
56
|
+
error='API key not found',
|
57
|
+
resource_id=api_key_id,
|
58
|
+
increment=increment
|
59
|
+
)
|
60
|
+
|
61
|
+
# Get updated values for logging
|
62
|
+
api_key = APIKey.objects.get(id=api_key_id)
|
63
|
+
|
64
|
+
processing_time = (time.time() - start_time) * 1000
|
65
|
+
|
66
|
+
logger.debug(f"API key usage updated", extra={
|
67
|
+
'api_key_id': api_key_id,
|
68
|
+
'user_id': api_key.user.id,
|
69
|
+
'total_requests': api_key.total_requests,
|
70
|
+
'increment': increment,
|
71
|
+
'ip_address': ip_address,
|
72
|
+
'processing_time_ms': round(processing_time, 2)
|
73
|
+
})
|
74
|
+
|
75
|
+
return UsageUpdateResult(
|
76
|
+
status='success',
|
77
|
+
resource_id=api_key_id,
|
78
|
+
total_requests=api_key.total_requests,
|
79
|
+
increment=increment,
|
80
|
+
user_id=api_key.user.id,
|
81
|
+
processing_time_ms=round(processing_time, 2)
|
82
|
+
)
|
83
|
+
|
84
|
+
except Exception as e:
|
85
|
+
logger.error(f"Failed to update API key usage", extra={
|
86
|
+
'api_key_id': api_key_id,
|
87
|
+
'error': str(e),
|
88
|
+
'ip_address': ip_address
|
89
|
+
})
|
90
|
+
raise # Re-raise for Dramatiq retry logic
|
91
|
+
|
92
|
+
@dramatiq.actor(queue_name="payments")
|
93
|
+
def update_subscription_usage_async(
|
94
|
+
subscription_id: str,
|
95
|
+
increment: int = 1
|
96
|
+
) -> UsageUpdateResult:
|
97
|
+
"""
|
98
|
+
Update subscription usage counters asynchronously.
|
99
|
+
|
100
|
+
Args:
|
101
|
+
subscription_id: Subscription UUID to update
|
102
|
+
increment: Number to increment (default: 1)
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
Update result with statistics
|
106
|
+
"""
|
107
|
+
start_time = time.time()
|
108
|
+
|
109
|
+
try:
|
110
|
+
with transaction.atomic():
|
111
|
+
# Use F() expressions for atomic updates
|
112
|
+
updated_count = Subscription.objects.filter(id=subscription_id).update(
|
113
|
+
total_requests=F('total_requests') + increment,
|
114
|
+
last_request_at=timezone.now(),
|
115
|
+
updated_at=timezone.now()
|
116
|
+
)
|
117
|
+
|
118
|
+
if updated_count == 0:
|
119
|
+
logger.warning(f"Subscription not found: {subscription_id}")
|
120
|
+
return UsageUpdateResult(
|
121
|
+
status='error',
|
122
|
+
error='Subscription not found',
|
123
|
+
resource_id=subscription_id,
|
124
|
+
increment=increment
|
125
|
+
)
|
126
|
+
|
127
|
+
# Get updated values for logging
|
128
|
+
subscription = Subscription.objects.get(id=subscription_id)
|
129
|
+
|
130
|
+
processing_time = (time.time() - start_time) * 1000
|
131
|
+
|
132
|
+
logger.debug(f"Subscription usage updated", extra={
|
133
|
+
'subscription_id': subscription_id,
|
134
|
+
'user_id': subscription.user.id,
|
135
|
+
'total_requests': subscription.total_requests,
|
136
|
+
'increment': increment,
|
137
|
+
'processing_time_ms': round(processing_time, 2)
|
138
|
+
})
|
139
|
+
|
140
|
+
return UsageUpdateResult(
|
141
|
+
status='success',
|
142
|
+
resource_id=subscription_id,
|
143
|
+
total_requests=subscription.total_requests,
|
144
|
+
increment=increment,
|
145
|
+
user_id=subscription.user.id,
|
146
|
+
processing_time_ms=round(processing_time, 2)
|
147
|
+
)
|
148
|
+
|
149
|
+
except Exception as e:
|
150
|
+
logger.error(f"Failed to update subscription usage", extra={
|
151
|
+
'subscription_id': subscription_id,
|
152
|
+
'error': str(e)
|
153
|
+
})
|
154
|
+
raise # Re-raise for Dramatiq retry logic
|
155
|
+
|
156
|
+
@dramatiq.actor(queue_name="payments")
|
157
|
+
def batch_update_usage_counters() -> BatchUpdateResult:
|
158
|
+
"""
|
159
|
+
Batch update usage counters from cache to reduce database load.
|
160
|
+
|
161
|
+
This task processes accumulated usage data from Redis cache
|
162
|
+
and performs batch updates to the database.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
Batch processing results
|
166
|
+
"""
|
167
|
+
start_time = time.time()
|
168
|
+
api_keys_updated = 0
|
169
|
+
subscriptions_updated = 0
|
170
|
+
errors = []
|
171
|
+
|
172
|
+
try:
|
173
|
+
# Process API key usage counters
|
174
|
+
api_key_pattern = "api_usage_pending:*"
|
175
|
+
api_key_keys = cache.keys(api_key_pattern)
|
176
|
+
|
177
|
+
for cache_key in api_key_keys:
|
178
|
+
try:
|
179
|
+
# Extract API key ID from cache key
|
180
|
+
api_key_id = cache_key.split(':')[-1]
|
181
|
+
pending_count = cache.get(cache_key, 0)
|
182
|
+
|
183
|
+
if pending_count > 0:
|
184
|
+
# Update in background
|
185
|
+
update_api_key_usage_async.send(
|
186
|
+
api_key_id=api_key_id,
|
187
|
+
increment=pending_count
|
188
|
+
)
|
189
|
+
|
190
|
+
# Clear cache
|
191
|
+
cache.delete(cache_key)
|
192
|
+
api_keys_updated += 1
|
193
|
+
|
194
|
+
except Exception as e:
|
195
|
+
errors.append({
|
196
|
+
'type': 'api_key',
|
197
|
+
'cache_key': cache_key,
|
198
|
+
'error': str(e)
|
199
|
+
})
|
200
|
+
|
201
|
+
# Process subscription usage counters
|
202
|
+
subscription_pattern = "subscription_usage_pending:*"
|
203
|
+
subscription_keys = cache.keys(subscription_pattern)
|
204
|
+
|
205
|
+
for cache_key in subscription_keys:
|
206
|
+
try:
|
207
|
+
# Extract subscription ID from cache key
|
208
|
+
subscription_id = cache_key.split(':')[-1]
|
209
|
+
pending_count = cache.get(cache_key, 0)
|
210
|
+
|
211
|
+
if pending_count > 0:
|
212
|
+
# Update in background
|
213
|
+
update_subscription_usage_async.send(
|
214
|
+
subscription_id=subscription_id,
|
215
|
+
increment=pending_count
|
216
|
+
)
|
217
|
+
|
218
|
+
# Clear cache
|
219
|
+
cache.delete(cache_key)
|
220
|
+
subscriptions_updated += 1
|
221
|
+
|
222
|
+
except Exception as e:
|
223
|
+
errors.append({
|
224
|
+
'type': 'subscription',
|
225
|
+
'cache_key': cache_key,
|
226
|
+
'error': str(e)
|
227
|
+
})
|
228
|
+
|
229
|
+
processing_time = (time.time() - start_time) * 1000
|
230
|
+
|
231
|
+
logger.info(f"Batch usage update completed", extra={
|
232
|
+
'api_keys_updated': api_keys_updated,
|
233
|
+
'subscriptions_updated': subscriptions_updated,
|
234
|
+
'errors_count': len(errors),
|
235
|
+
'processing_time_ms': round(processing_time, 2)
|
236
|
+
})
|
237
|
+
|
238
|
+
return BatchUpdateResult(
|
239
|
+
status='success',
|
240
|
+
api_keys_updated=api_keys_updated,
|
241
|
+
subscriptions_updated=subscriptions_updated,
|
242
|
+
errors=errors,
|
243
|
+
total_items=api_keys_updated + subscriptions_updated,
|
244
|
+
processing_time_ms=round(processing_time, 2)
|
245
|
+
)
|
246
|
+
|
247
|
+
except Exception as e:
|
248
|
+
logger.error(f"Batch usage update failed: {e}")
|
249
|
+
errors.append({
|
250
|
+
'type': 'batch_processing',
|
251
|
+
'error': str(e)
|
252
|
+
})
|
253
|
+
return BatchUpdateResult(
|
254
|
+
status='error',
|
255
|
+
api_keys_updated=api_keys_updated,
|
256
|
+
subscriptions_updated=subscriptions_updated,
|
257
|
+
errors=errors,
|
258
|
+
total_items=api_keys_updated + subscriptions_updated,
|
259
|
+
error=str(e)
|
260
|
+
)
|
261
|
+
|
262
|
+
@dramatiq.actor(queue_name="payments")
|
263
|
+
def cleanup_stale_usage_cache() -> CleanupResult:
|
264
|
+
"""
|
265
|
+
Cleanup stale usage tracking cache entries.
|
266
|
+
|
267
|
+
Removes old cache entries that might have been left behind
|
268
|
+
due to processing errors or system restarts.
|
269
|
+
|
270
|
+
Returns:
|
271
|
+
Cleanup results
|
272
|
+
"""
|
273
|
+
try:
|
274
|
+
cleanup_count = 0
|
275
|
+
|
276
|
+
# Cleanup old API key usage cache
|
277
|
+
api_key_keys = cache.keys("api_usage_pending:*")
|
278
|
+
for key in api_key_keys:
|
279
|
+
# Check if cache entry is older than 1 hour
|
280
|
+
ttl = cache.ttl(key)
|
281
|
+
if ttl is not None and ttl < 3600: # Less than 1 hour remaining
|
282
|
+
cache.delete(key)
|
283
|
+
cleanup_count += 1
|
284
|
+
|
285
|
+
# Cleanup old subscription usage cache
|
286
|
+
subscription_keys = cache.keys("subscription_usage_pending:*")
|
287
|
+
for key in subscription_keys:
|
288
|
+
ttl = cache.ttl(key)
|
289
|
+
if ttl is not None and ttl < 3600:
|
290
|
+
cache.delete(key)
|
291
|
+
cleanup_count += 1
|
292
|
+
|
293
|
+
logger.info(f"Cleaned up {cleanup_count} stale cache entries")
|
294
|
+
|
295
|
+
return CleanupResult(
|
296
|
+
status='completed',
|
297
|
+
cleaned_entries=cleanup_count,
|
298
|
+
cleanup_type='stale_usage_cache'
|
299
|
+
)
|
300
|
+
|
301
|
+
except Exception as e:
|
302
|
+
logger.error(f"Cache cleanup failed: {e}")
|
303
|
+
return CleanupResult(
|
304
|
+
status='error',
|
305
|
+
error=str(e),
|
306
|
+
cleaned_entries=0,
|
307
|
+
cleanup_type='stale_usage_cache'
|
308
|
+
)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Reusable dashboard header component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="Payment Overview" icon="💳" %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- title: Dashboard title
|
9
|
+
- icon: Emoji or icon to display
|
10
|
+
- subtitle: Optional subtitle
|
11
|
+
{% endcomment %}
|
12
|
+
|
13
|
+
<div class="bg-white border border-base-200 dark:bg-base-900 dark:border-base-700 p-4 rounded-default mb-4">
|
14
|
+
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark mb-3">
|
15
|
+
{% if icon %}{{ icon }} {% endif %}{{ title }}
|
16
|
+
{% if subtitle %}
|
17
|
+
<span class="text-sm font-normal text-font-subtle-light dark:text-font-subtle-dark">{{ subtitle }}</span>
|
18
|
+
{% endif %}
|
19
|
+
</h3>
|
20
|
+
|
21
|
+
{% block dashboard_content %}
|
22
|
+
{% endblock %}
|
23
|
+
</div>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Reusable stats card component for payment admin dashboards
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Users" value="1,234" color="primary" icon="👥" %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- title: Card title
|
9
|
+
- value: Main value to display
|
10
|
+
- color: Color theme (primary, success, warning, error, info)
|
11
|
+
- icon: Emoji or icon to display
|
12
|
+
- subtitle: Optional subtitle text
|
13
|
+
{% endcomment %}
|
14
|
+
|
15
|
+
{% load humanize %}
|
16
|
+
|
17
|
+
<div class="text-center bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
18
|
+
<p class="text-2xl font-bold text-{{ color|default:'primary' }}-600 dark:text-{{ color|default:'primary' }}-400">
|
19
|
+
{% if icon %}<span class="mr-1">{{ icon }}</span>{% endif %}{{ value|default:0 }}
|
20
|
+
</p>
|
21
|
+
<p class="text-sm text-font-subtle-light dark:text-font-subtle-dark">{{ title }}</p>
|
22
|
+
{% if subtitle %}
|
23
|
+
<p class="text-xs text-gray-500">{{ subtitle }}</p>
|
24
|
+
{% endif %}
|
25
|
+
</div>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Reusable stats grid component for payment admin dashboards
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include "admin/payments/_components/stats_grid.html" with stats=stats_data columns=4 %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- stats: List of stat objects with title, value, color, icon properties
|
9
|
+
- columns: Number of columns (2, 3, 4, 5)
|
10
|
+
{% endcomment %}
|
11
|
+
|
12
|
+
<div class="grid grid-cols-{{ columns|default:4 }} gap-4 mb-4">
|
13
|
+
{% for stat in stats %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title=stat.title value=stat.value color=stat.color icon=stat.icon subtitle=stat.subtitle %}
|
15
|
+
{% endfor %}
|
16
|
+
</div>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
{% extends "admin/change_list.html" %}
|
2
|
+
{% load static %}
|
3
|
+
|
4
|
+
{% block result_list %}
|
5
|
+
<!-- API Key Statistics Dashboard -->
|
6
|
+
{% include "admin/payments/_components/dashboard_header.html" with title="API Keys Overview" icon="🔑" %}
|
7
|
+
|
8
|
+
{% if api_key_stats %}
|
9
|
+
{% with stats=api_key_stats %}
|
10
|
+
<div class="grid grid-cols-4 gap-4 mb-4">
|
11
|
+
{% include "admin/payments/_components/stats_card.html" with title="Total Keys" value=stats.total_keys color="primary" %}
|
12
|
+
{% include "admin/payments/_components/stats_card.html" with title="Active" value=stats.active_keys color="success" %}
|
13
|
+
{% include "admin/payments/_components/stats_card.html" with title="Expiring Soon" value=stats.expiring_soon color="warning" %}
|
14
|
+
{% include "admin/payments/_components/stats_card.html" with title="Expired" value=stats.expired_keys color="error" %}
|
15
|
+
</div>
|
16
|
+
{% endwith %}
|
17
|
+
{% endif %}
|
18
|
+
|
19
|
+
<div class="grid grid-cols-2 gap-4">
|
20
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
21
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">Usage Statistics</h4>
|
22
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
23
|
+
<li>📊 Total Requests: <span class="font-medium text-primary-600 dark:text-primary-400">{{ stats.total_requests|default:0 }}</span></li>
|
24
|
+
<li>📈 Recently Used: <span class="font-medium text-success-600 dark:text-success-400">{{ stats.recently_used|default:0 }}</span></li>
|
25
|
+
<li>🆕 Unused: <span class="font-medium text-warning-600 dark:text-warning-400">{{ stats.unused_keys|default:0 }}</span></li>
|
26
|
+
</ul>
|
27
|
+
</div>
|
28
|
+
<div class="bg-base-50 dark:bg-base-800 p-3 rounded-default">
|
29
|
+
<h4 class="font-semibold text-font-important-light dark:text-font-important-dark mb-2">🔒 Security</h4>
|
30
|
+
<ul class="text-sm space-y-1 text-font-default-light dark:text-font-default-dark">
|
31
|
+
<li>🔥 Heavy Usage: <span class="font-medium text-purple-600 dark:text-purple-400">{{ stats.heavy_usage_keys|default:0 }}</span></li>
|
32
|
+
<li>⚠️ Old Unused: <span class="font-medium text-error-600 dark:text-error-400">{{ stats.never_used_old_keys|default:0 }}</span></li>
|
33
|
+
<li>👥 Top Users: <span class="font-medium text-info-600 dark:text-info-400">{{ stats.top_users|length|default:0 }}</span></li>
|
34
|
+
</ul>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
|
38
|
+
{{ block.super }}
|
39
|
+
{% endblock %}
|