django-cfg 1.5.14__py3-none-any.whl → 1.5.20__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +2 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +0 -79
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +257 -0
- django_cfg/apps/integrations/grpc/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/centrifugo/bridge.py +277 -0
- django_cfg/apps/integrations/grpc/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/interceptors/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +55 -0
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +311 -7
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- django_cfg/apps/integrations/grpc/services/discovery.py +7 -1
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +206 -5
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +12 -0
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/frontend/admin.zip +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/METADATA +1 -1
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/RECORD +53 -37
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.20.dist-info}/licenses/LICENSE +0 -0
|
@@ -53,6 +53,10 @@ class ServiceDiscovery:
|
|
|
53
53
|
# Get config from django-cfg using Pydantic2 pattern
|
|
54
54
|
self.config = get_grpc_config()
|
|
55
55
|
|
|
56
|
+
logger.warning(f"🔍 ServiceDiscovery.__init__: config = {self.config}")
|
|
57
|
+
if self.config:
|
|
58
|
+
logger.warning(f"🔍 handlers_hook = {self.config.handlers_hook}")
|
|
59
|
+
|
|
56
60
|
if self.config:
|
|
57
61
|
self.auto_register = self.config.auto_register_apps
|
|
58
62
|
self.enabled_apps = self.config.enabled_apps if self.config.auto_register_apps else []
|
|
@@ -452,11 +456,13 @@ class ServiceDiscovery:
|
|
|
452
456
|
>>> if handlers_hook:
|
|
453
457
|
... services = handlers_hook(server)
|
|
454
458
|
"""
|
|
459
|
+
logger.warning(f"🔍 get_handlers_hook called")
|
|
455
460
|
if not self.config:
|
|
456
|
-
logger.
|
|
461
|
+
logger.warning("❌ No gRPC config available")
|
|
457
462
|
return None
|
|
458
463
|
|
|
459
464
|
handlers_hook_path = self.config.handlers_hook
|
|
465
|
+
logger.warning(f"🔍 handlers_hook_path = '{handlers_hook_path}'")
|
|
460
466
|
|
|
461
467
|
if not handlers_hook_path:
|
|
462
468
|
logger.debug("No handlers_hook configured")
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Server Lifecycle Logging Utilities
|
|
2
|
+
|
|
3
|
+
Reusable functions for logging server startup and shutdown with timestamps and uptime tracking.
|
|
4
|
+
|
|
5
|
+
**Uses Rich** for beautiful output with panels, tables, and colors! 🎨
|
|
6
|
+
|
|
7
|
+
## 📦 Functions
|
|
8
|
+
|
|
9
|
+
### `log_server_start()`
|
|
10
|
+
|
|
11
|
+
Logs server startup with timestamp and configuration.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_start
|
|
15
|
+
|
|
16
|
+
start_time = log_server_start(
|
|
17
|
+
logger,
|
|
18
|
+
server_type="gRPC Server", # Server type
|
|
19
|
+
mode="Development", # Production/Development
|
|
20
|
+
hotreload_enabled=True, # Is hotreload enabled?
|
|
21
|
+
host="0.0.0.0", # Additional parameters
|
|
22
|
+
port=50051
|
|
23
|
+
)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Output (Rich Panel):**
|
|
27
|
+
```
|
|
28
|
+
╭────────────── 🚀 gRPC Server Starting ───────────────╮
|
|
29
|
+
│ │
|
|
30
|
+
│ ⏰ Started at 2025-11-05 14:30:15 │
|
|
31
|
+
│ Mode Development │
|
|
32
|
+
│ Hotreload Enabled ⚡ │
|
|
33
|
+
│ Host 0.0.0.0 │
|
|
34
|
+
│ Port 50051 │
|
|
35
|
+
│ │
|
|
36
|
+
╰──────────────────────────────────────────────────────╯
|
|
37
|
+
⚠️ Hotreload active - connections may be dropped on code changes
|
|
38
|
+
```
|
|
39
|
+
(with green border and colors!)
|
|
40
|
+
|
|
41
|
+
**Returns:** `datetime` object of start time (for use in `log_server_shutdown`)
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### `log_server_shutdown()`
|
|
46
|
+
|
|
47
|
+
Logs server shutdown with uptime calculation.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
|
|
51
|
+
|
|
52
|
+
log_server_shutdown(
|
|
53
|
+
logger,
|
|
54
|
+
start_time, # datetime from log_server_start()
|
|
55
|
+
server_type="gRPC Server",
|
|
56
|
+
reason="Hotreload triggered", # Shutdown reason
|
|
57
|
+
active_connections=5 # Additional parameters
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Output (Rich Panel):**
|
|
62
|
+
```
|
|
63
|
+
╭───────────── 🧹 Shutting down gRPC Server ──────────────╮
|
|
64
|
+
│ │
|
|
65
|
+
│ 📋 Reason Hotreload triggered │
|
|
66
|
+
│ ⏱️ Uptime 0h 2m 35s │
|
|
67
|
+
│ 🕐 Stopped at 2025-11-05 14:32:50 │
|
|
68
|
+
│ Active Connections 5 │
|
|
69
|
+
│ │
|
|
70
|
+
╰──────────────────────────────────────────────────────────╯
|
|
71
|
+
✅ Server shutdown complete
|
|
72
|
+
```
|
|
73
|
+
(with red border and colors!)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 🎯 Complete Usage Example
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from django_cfg.apps.integrations.grpc.utils import (
|
|
81
|
+
setup_streaming_logger,
|
|
82
|
+
log_server_start,
|
|
83
|
+
log_server_shutdown,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Create logger
|
|
87
|
+
logger = setup_streaming_logger('my_server')
|
|
88
|
+
|
|
89
|
+
# Log server start
|
|
90
|
+
start_time = log_server_start(
|
|
91
|
+
logger,
|
|
92
|
+
server_type="WebSocket Server",
|
|
93
|
+
mode="Production",
|
|
94
|
+
hotreload_enabled=False,
|
|
95
|
+
host="0.0.0.0",
|
|
96
|
+
port=8000
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Server work...
|
|
101
|
+
await server.serve_forever()
|
|
102
|
+
shutdown_reason = "Normal termination"
|
|
103
|
+
except KeyboardInterrupt:
|
|
104
|
+
shutdown_reason = "Keyboard interrupt"
|
|
105
|
+
finally:
|
|
106
|
+
# Log server shutdown
|
|
107
|
+
log_server_shutdown(
|
|
108
|
+
logger,
|
|
109
|
+
start_time,
|
|
110
|
+
server_type="WebSocket Server",
|
|
111
|
+
reason=shutdown_reason,
|
|
112
|
+
total_connections=1250
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 📊 Used in:
|
|
119
|
+
|
|
120
|
+
- ✅ `rungrpc` - gRPC server command
|
|
121
|
+
- ✅ Can be used for any servers (WebSocket, HTTP, etc.)
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 🔧 Parameters
|
|
126
|
+
|
|
127
|
+
### `log_server_start()`
|
|
128
|
+
|
|
129
|
+
| Parameter | Type | Required | Description |
|
|
130
|
+
|----------|-----|----------|-------------|
|
|
131
|
+
| `logger` | `logging.Logger` | ✅ Yes | Logger instance |
|
|
132
|
+
| `server_type` | `str` | ❌ No | Server type (default: "Server") |
|
|
133
|
+
| `mode` | `str` | ❌ No | Running mode (default: "Development") |
|
|
134
|
+
| `hotreload_enabled` | `bool` | ❌ No | Is hotreload enabled (default: False) |
|
|
135
|
+
| `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
|
|
136
|
+
| `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
|
|
137
|
+
|
|
138
|
+
### `log_server_shutdown()`
|
|
139
|
+
|
|
140
|
+
| Parameter | Type | Required | Description |
|
|
141
|
+
|----------|-----|----------|-------------|
|
|
142
|
+
| `logger` | `logging.Logger` | ✅ Yes | Logger instance |
|
|
143
|
+
| `start_time` | `datetime` | ✅ Yes | Start time from `log_server_start()` |
|
|
144
|
+
| `server_type` | `str` | ❌ No | Server type (default: "Server") |
|
|
145
|
+
| `reason` | `str` | ❌ No | Shutdown reason |
|
|
146
|
+
| `use_rich` | `bool` | ❌ No | Use Rich for output (default: True) |
|
|
147
|
+
| `**extra_info` | `dict` | ❌ No | Additional key-value pairs to log |
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 💡 Benefits
|
|
152
|
+
|
|
153
|
+
1. ✅ **DRY** - single code for all servers
|
|
154
|
+
2. ✅ **Consistency** - uniform log format
|
|
155
|
+
3. ✅ **Rich UI** - beautiful panels, tables, colors 🎨
|
|
156
|
+
4. ✅ **Uptime tracking** - automatic time calculation
|
|
157
|
+
5. ✅ **Flexible** - can add arbitrary parameters via `**extra_info`
|
|
158
|
+
6. ✅ **Hotreload aware** - special warning for hotreload mode
|
|
159
|
+
7. ✅ **Fallback** - works without Rich (if `use_rich=False`)
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
**Author:** django-cfg team
|
|
164
|
+
**Date:** 2025-11-05
|
|
@@ -12,6 +12,12 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Optional
|
|
13
13
|
from django.utils import timezone
|
|
14
14
|
|
|
15
|
+
# Rich for beautiful console output
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from rich.table import Table
|
|
19
|
+
from rich.text import Text
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
class AutoTracebackHandler(logging.Handler):
|
|
17
23
|
"""
|
|
@@ -128,11 +134,14 @@ def setup_streaming_logger(
|
|
|
128
134
|
file_handler = AutoTracebackHandler(base_file_handler)
|
|
129
135
|
streaming_logger.addHandler(file_handler)
|
|
130
136
|
|
|
131
|
-
# Console handler - important messages only
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
# Console handler - important messages only (also with auto-traceback)
|
|
138
|
+
base_console_handler = logging.StreamHandler()
|
|
139
|
+
base_console_handler.setLevel(console_level)
|
|
134
140
|
console_formatter = logging.Formatter('%(levelname)s: %(message)s')
|
|
135
|
-
|
|
141
|
+
base_console_handler.setFormatter(console_formatter)
|
|
142
|
+
|
|
143
|
+
# Wrap console handler with auto-traceback too
|
|
144
|
+
console_handler = AutoTracebackHandler(base_console_handler)
|
|
136
145
|
streaming_logger.addHandler(console_handler)
|
|
137
146
|
|
|
138
147
|
# Prevent propagation to avoid duplicate logs
|
|
@@ -174,4 +183,196 @@ def get_streaming_logger(name: str = "grpc_streaming") -> logging.Logger:
|
|
|
174
183
|
return logger
|
|
175
184
|
|
|
176
185
|
|
|
177
|
-
|
|
186
|
+
def log_server_start(
|
|
187
|
+
logger: logging.Logger,
|
|
188
|
+
server_type: str = "Server",
|
|
189
|
+
mode: str = "Development",
|
|
190
|
+
hotreload_enabled: bool = False,
|
|
191
|
+
use_rich: bool = True,
|
|
192
|
+
**extra_info
|
|
193
|
+
):
|
|
194
|
+
"""
|
|
195
|
+
Log server startup with timestamp and configuration using Rich panels.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
logger: Logger instance to use
|
|
199
|
+
server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
|
|
200
|
+
mode: Running mode (Development/Production)
|
|
201
|
+
hotreload_enabled: Whether hot-reload is enabled
|
|
202
|
+
use_rich: Use Rich for beautiful output (default: True)
|
|
203
|
+
**extra_info: Additional key-value pairs to log
|
|
204
|
+
|
|
205
|
+
Example:
|
|
206
|
+
```python
|
|
207
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_start
|
|
208
|
+
from datetime import datetime
|
|
209
|
+
|
|
210
|
+
start_time = log_server_start(
|
|
211
|
+
logger,
|
|
212
|
+
server_type="gRPC Server",
|
|
213
|
+
mode="Development",
|
|
214
|
+
hotreload_enabled=True,
|
|
215
|
+
host="0.0.0.0",
|
|
216
|
+
port=50051
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
datetime object of start time for later use in log_server_shutdown
|
|
222
|
+
"""
|
|
223
|
+
from datetime import datetime
|
|
224
|
+
|
|
225
|
+
start_time = datetime.now()
|
|
226
|
+
|
|
227
|
+
if use_rich:
|
|
228
|
+
# Create Rich table for server info
|
|
229
|
+
console = Console()
|
|
230
|
+
|
|
231
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
232
|
+
table.add_column("Key", style="cyan")
|
|
233
|
+
table.add_column("Value", style="white")
|
|
234
|
+
|
|
235
|
+
table.add_row("⏰ Started at", start_time.strftime('%Y-%m-%d %H:%M:%S'))
|
|
236
|
+
table.add_row("Mode", f"[{'red' if mode == 'Production' else 'green'}]{mode}[/]")
|
|
237
|
+
table.add_row("Hotreload", f"[{'yellow' if hotreload_enabled else 'dim'}]{'Enabled ⚡' if hotreload_enabled else 'Disabled'}[/]")
|
|
238
|
+
|
|
239
|
+
# Add extra info
|
|
240
|
+
for key, value in extra_info.items():
|
|
241
|
+
key_display = key.replace('_', ' ').title()
|
|
242
|
+
table.add_row(key_display, str(value))
|
|
243
|
+
|
|
244
|
+
# Create panel
|
|
245
|
+
panel = Panel(
|
|
246
|
+
table,
|
|
247
|
+
title=f"[bold green]🚀 {server_type} Starting[/bold green]",
|
|
248
|
+
border_style="green",
|
|
249
|
+
padding=(1, 2)
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
console.print(panel)
|
|
253
|
+
|
|
254
|
+
if hotreload_enabled:
|
|
255
|
+
console.print(
|
|
256
|
+
"[yellow]⚠️ Hotreload active - connections may be dropped on code changes[/yellow]",
|
|
257
|
+
style="bold"
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
# Fallback to simple logging
|
|
261
|
+
logger.info("=" * 80)
|
|
262
|
+
logger.info(f"🚀 {server_type} Starting")
|
|
263
|
+
logger.info(f" ⏰ Started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
264
|
+
logger.info(f" Mode: {mode}")
|
|
265
|
+
logger.info(f" Hotreload: {'Enabled' if hotreload_enabled else 'Disabled'}")
|
|
266
|
+
|
|
267
|
+
for key, value in extra_info.items():
|
|
268
|
+
key_display = key.replace('_', ' ').title()
|
|
269
|
+
logger.info(f" {key_display}: {value}")
|
|
270
|
+
|
|
271
|
+
if hotreload_enabled:
|
|
272
|
+
logger.warning(
|
|
273
|
+
"⚠️ Hotreload active - connections may be dropped on code changes"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
logger.info("=" * 80)
|
|
277
|
+
|
|
278
|
+
return start_time
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def log_server_shutdown(
|
|
282
|
+
logger: logging.Logger,
|
|
283
|
+
start_time,
|
|
284
|
+
server_type: str = "Server",
|
|
285
|
+
reason: str = None,
|
|
286
|
+
use_rich: bool = True,
|
|
287
|
+
**extra_info
|
|
288
|
+
):
|
|
289
|
+
"""
|
|
290
|
+
Log server shutdown with uptime calculation using Rich panels.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
logger: Logger instance to use
|
|
294
|
+
start_time: datetime object from log_server_start()
|
|
295
|
+
server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
|
|
296
|
+
reason: Shutdown reason (e.g., "Keyboard interrupt", "Hotreload")
|
|
297
|
+
use_rich: Use Rich for beautiful output (default: True)
|
|
298
|
+
**extra_info: Additional key-value pairs to log
|
|
299
|
+
|
|
300
|
+
Example:
|
|
301
|
+
```python
|
|
302
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
|
|
303
|
+
|
|
304
|
+
log_server_shutdown(
|
|
305
|
+
logger,
|
|
306
|
+
start_time,
|
|
307
|
+
server_type="gRPC Server",
|
|
308
|
+
reason="Hotreload triggered",
|
|
309
|
+
active_connections=5
|
|
310
|
+
)
|
|
311
|
+
```
|
|
312
|
+
"""
|
|
313
|
+
from datetime import datetime
|
|
314
|
+
|
|
315
|
+
end_time = datetime.now()
|
|
316
|
+
uptime = end_time - start_time
|
|
317
|
+
uptime_seconds = int(uptime.total_seconds())
|
|
318
|
+
|
|
319
|
+
# Format uptime
|
|
320
|
+
hours = uptime_seconds // 3600
|
|
321
|
+
minutes = (uptime_seconds % 3600) // 60
|
|
322
|
+
seconds = uptime_seconds % 60
|
|
323
|
+
uptime_str = f"{hours}h {minutes}m {seconds}s"
|
|
324
|
+
|
|
325
|
+
if use_rich:
|
|
326
|
+
# Create Rich table for shutdown info
|
|
327
|
+
console = Console()
|
|
328
|
+
|
|
329
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
330
|
+
table.add_column("Key", style="cyan")
|
|
331
|
+
table.add_column("Value", style="white")
|
|
332
|
+
|
|
333
|
+
if reason:
|
|
334
|
+
table.add_row("📋 Reason", reason)
|
|
335
|
+
|
|
336
|
+
table.add_row("⏱️ Uptime", f"[bold]{uptime_str}[/bold]")
|
|
337
|
+
table.add_row("🕐 Stopped at", end_time.strftime('%Y-%m-%d %H:%M:%S'))
|
|
338
|
+
|
|
339
|
+
# Add extra info
|
|
340
|
+
for key, value in extra_info.items():
|
|
341
|
+
key_display = key.replace('_', ' ').title()
|
|
342
|
+
table.add_row(key_display, str(value))
|
|
343
|
+
|
|
344
|
+
# Create panel
|
|
345
|
+
panel = Panel(
|
|
346
|
+
table,
|
|
347
|
+
title=f"[bold red]🧹 Shutting down {server_type}[/bold red]",
|
|
348
|
+
border_style="red",
|
|
349
|
+
padding=(1, 2)
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
console.print(panel)
|
|
353
|
+
console.print("[green]✅ Server shutdown complete[/green]", style="bold")
|
|
354
|
+
else:
|
|
355
|
+
# Fallback to simple logging
|
|
356
|
+
logger.info("=" * 80)
|
|
357
|
+
logger.info(f"🧹 Shutting down {server_type}...")
|
|
358
|
+
|
|
359
|
+
if reason:
|
|
360
|
+
logger.info(f" 📋 Reason: {reason}")
|
|
361
|
+
|
|
362
|
+
logger.info(f" ⏱️ Uptime: {uptime_str}")
|
|
363
|
+
logger.info(f" 🕐 Stopped at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
364
|
+
|
|
365
|
+
for key, value in extra_info.items():
|
|
366
|
+
key_display = key.replace('_', ' ').title()
|
|
367
|
+
logger.info(f" {key_display}: {value}")
|
|
368
|
+
|
|
369
|
+
logger.info("✅ Server shutdown complete")
|
|
370
|
+
logger.info("=" * 80)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
__all__ = [
|
|
374
|
+
"setup_streaming_logger",
|
|
375
|
+
"get_streaming_logger",
|
|
376
|
+
"log_server_start",
|
|
377
|
+
"log_server_shutdown",
|
|
378
|
+
]
|
|
@@ -5,6 +5,8 @@ Serializers for displaying user's DjangoConfig settings.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from rest_framework import serializers
|
|
8
|
+
from drf_spectacular.utils import extend_schema_field
|
|
9
|
+
from drf_spectacular.types import OpenApiTypes
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
# Nested serializers for complex structures
|
|
@@ -89,6 +91,18 @@ class RedisQueueConfigSerializer(serializers.Serializer):
|
|
|
89
91
|
socket_timeout = serializers.IntegerField(required=False, allow_null=True)
|
|
90
92
|
|
|
91
93
|
|
|
94
|
+
class RQScheduleSerializer(serializers.Serializer):
|
|
95
|
+
"""Redis Queue schedule configuration."""
|
|
96
|
+
func = serializers.CharField(required=False, allow_null=True)
|
|
97
|
+
cron_string = serializers.CharField(required=False, allow_null=True)
|
|
98
|
+
queue = serializers.CharField(required=False, allow_null=True)
|
|
99
|
+
kwargs = serializers.DictField(required=False, allow_null=True)
|
|
100
|
+
args = serializers.ListField(required=False, allow_null=True)
|
|
101
|
+
meta = serializers.DictField(required=False, allow_null=True)
|
|
102
|
+
repeat = serializers.IntegerField(required=False, allow_null=True)
|
|
103
|
+
result_ttl = serializers.IntegerField(required=False, allow_null=True)
|
|
104
|
+
|
|
105
|
+
|
|
92
106
|
class DjangoRQConfigSerializer(serializers.Serializer):
|
|
93
107
|
"""Django-RQ configuration."""
|
|
94
108
|
enabled = serializers.BooleanField(required=False, allow_null=True)
|
|
@@ -97,7 +111,7 @@ class DjangoRQConfigSerializer(serializers.Serializer):
|
|
|
97
111
|
exception_handlers = serializers.ListField(required=False, allow_null=True)
|
|
98
112
|
api_token = serializers.CharField(required=False, allow_null=True)
|
|
99
113
|
prometheus_enabled = serializers.BooleanField(required=False, allow_null=True)
|
|
100
|
-
schedules = serializers.ListField(child=
|
|
114
|
+
schedules = serializers.ListField(child=RQScheduleSerializer(), required=False, allow_null=True)
|
|
101
115
|
|
|
102
116
|
|
|
103
117
|
class DRFConfigSerializer(serializers.Serializer):
|
|
@@ -126,6 +140,78 @@ class ConfigMetaSerializer(serializers.Serializer):
|
|
|
126
140
|
secret_key_configured = serializers.BooleanField()
|
|
127
141
|
|
|
128
142
|
|
|
143
|
+
class TelegramConfigSerializer(serializers.Serializer):
|
|
144
|
+
"""Telegram service configuration."""
|
|
145
|
+
bot_token = serializers.CharField(required=False, allow_null=True)
|
|
146
|
+
chat_id = serializers.IntegerField(required=False, allow_null=True)
|
|
147
|
+
parse_mode = serializers.CharField(required=False, allow_null=True)
|
|
148
|
+
disable_notification = serializers.BooleanField(required=False, allow_null=True)
|
|
149
|
+
disable_web_page_preview = serializers.BooleanField(required=False, allow_null=True)
|
|
150
|
+
timeout = serializers.IntegerField(required=False, allow_null=True)
|
|
151
|
+
webhook_url = serializers.CharField(required=False, allow_null=True)
|
|
152
|
+
webhook_secret = serializers.CharField(required=False, allow_null=True)
|
|
153
|
+
max_retries = serializers.IntegerField(required=False, allow_null=True)
|
|
154
|
+
retry_delay = serializers.FloatField(required=False, allow_null=True)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class NgrokConfigSerializer(serializers.Serializer):
|
|
158
|
+
"""Ngrok tunneling configuration."""
|
|
159
|
+
enabled = serializers.BooleanField(required=False, allow_null=True)
|
|
160
|
+
authtoken = serializers.CharField(required=False, allow_null=True)
|
|
161
|
+
basic_auth = serializers.ListField(required=False, allow_null=True)
|
|
162
|
+
compression = serializers.BooleanField(required=False, allow_null=True)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class AxesConfigSerializer(serializers.Serializer):
|
|
166
|
+
"""Django-Axes brute-force protection configuration."""
|
|
167
|
+
enabled = serializers.BooleanField(required=False, allow_null=True)
|
|
168
|
+
failure_limit = serializers.IntegerField(required=False, allow_null=True)
|
|
169
|
+
cooloff_time = serializers.IntegerField(required=False, allow_null=True)
|
|
170
|
+
lock_out_at_failure = serializers.BooleanField(required=False, allow_null=True)
|
|
171
|
+
reset_on_success = serializers.BooleanField(required=False, allow_null=True)
|
|
172
|
+
only_user_failures = serializers.BooleanField(required=False, allow_null=True)
|
|
173
|
+
lockout_template = serializers.CharField(required=False, allow_null=True)
|
|
174
|
+
lockout_url = serializers.CharField(required=False, allow_null=True)
|
|
175
|
+
verbose = serializers.BooleanField(required=False, allow_null=True)
|
|
176
|
+
enable_access_failure_log = serializers.BooleanField(required=False, allow_null=True)
|
|
177
|
+
ipware_proxy_count = serializers.IntegerField(required=False, allow_null=True)
|
|
178
|
+
ipware_meta_precedence_order = serializers.ListField(required=False, allow_null=True)
|
|
179
|
+
allowed_ips = serializers.ListField(required=False, allow_null=True)
|
|
180
|
+
denied_ips = serializers.ListField(required=False, allow_null=True)
|
|
181
|
+
cache_name = serializers.CharField(required=False, allow_null=True)
|
|
182
|
+
use_user_agent = serializers.BooleanField(required=False, allow_null=True)
|
|
183
|
+
username_form_field = serializers.CharField(required=False, allow_null=True)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class NextJSAdminConfigSerializer(serializers.Serializer):
|
|
187
|
+
"""Next.js Admin application configuration."""
|
|
188
|
+
enabled = serializers.BooleanField(required=False, allow_null=True)
|
|
189
|
+
url = serializers.CharField(required=False, allow_null=True)
|
|
190
|
+
api_base_url = serializers.CharField(required=False, allow_null=True)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class ConstanceConfigSerializer(serializers.Serializer):
|
|
194
|
+
"""Django-Constance dynamic settings configuration."""
|
|
195
|
+
config = serializers.DictField(required=False, allow_null=True)
|
|
196
|
+
config_fieldsets = serializers.DictField(required=False, allow_null=True)
|
|
197
|
+
backend = serializers.CharField(required=False, allow_null=True)
|
|
198
|
+
database_prefix = serializers.CharField(required=False, allow_null=True)
|
|
199
|
+
database_cache_backend = serializers.CharField(required=False, allow_null=True)
|
|
200
|
+
additional_config = serializers.DictField(required=False, allow_null=True)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class OpenAPIClientConfigSerializer(serializers.Serializer):
|
|
204
|
+
"""OpenAPI Client generation configuration."""
|
|
205
|
+
enabled = serializers.BooleanField(required=False, allow_null=True)
|
|
206
|
+
output_dir = serializers.CharField(required=False, allow_null=True)
|
|
207
|
+
client_name = serializers.CharField(required=False, allow_null=True)
|
|
208
|
+
schema_url = serializers.CharField(required=False, allow_null=True)
|
|
209
|
+
generator = serializers.CharField(required=False, allow_null=True)
|
|
210
|
+
additional_properties = serializers.DictField(required=False, allow_null=True)
|
|
211
|
+
templates = serializers.ListField(required=False, allow_null=True)
|
|
212
|
+
global_properties = serializers.DictField(required=False, allow_null=True)
|
|
213
|
+
|
|
214
|
+
|
|
129
215
|
class DjangoConfigSerializer(serializers.Serializer):
|
|
130
216
|
"""
|
|
131
217
|
Typed serializer for user's DjangoConfig settings.
|
|
@@ -188,10 +274,10 @@ class DjangoConfigSerializer(serializers.Serializer):
|
|
|
188
274
|
spectacular = SpectacularConfigSerializer(required=False, allow_null=True)
|
|
189
275
|
jwt = JWTConfigSerializer(required=False, allow_null=True)
|
|
190
276
|
|
|
191
|
-
#
|
|
192
|
-
telegram =
|
|
193
|
-
ngrok =
|
|
194
|
-
axes =
|
|
277
|
+
# Services & Security (now typed!)
|
|
278
|
+
telegram = TelegramConfigSerializer(required=False, allow_null=True)
|
|
279
|
+
ngrok = NgrokConfigSerializer(required=False, allow_null=True)
|
|
280
|
+
axes = AxesConfigSerializer(required=False, allow_null=True)
|
|
195
281
|
crypto_fields = serializers.DictField(required=False, allow_null=True)
|
|
196
282
|
unfold = serializers.CharField(required=False, allow_null=True) # String representation of Unfold config
|
|
197
283
|
tailwind_app_name = serializers.CharField(required=False, allow_null=True)
|
|
@@ -199,10 +285,10 @@ class DjangoConfigSerializer(serializers.Serializer):
|
|
|
199
285
|
limits = serializers.DictField(required=False, allow_null=True)
|
|
200
286
|
api_keys = serializers.DictField(required=False, allow_null=True)
|
|
201
287
|
custom_middleware = serializers.ListField(required=False, allow_null=True)
|
|
202
|
-
nextjs_admin =
|
|
288
|
+
nextjs_admin = NextJSAdminConfigSerializer(required=False, allow_null=True)
|
|
203
289
|
admin_emails = serializers.ListField(required=False, allow_null=True)
|
|
204
|
-
constance =
|
|
205
|
-
openapi_client =
|
|
290
|
+
constance = ConstanceConfigSerializer(required=False, allow_null=True)
|
|
291
|
+
openapi_client = OpenAPIClientConfigSerializer(required=False, allow_null=True)
|
|
206
292
|
|
|
207
293
|
# Metadata
|
|
208
294
|
_meta = ConfigMetaSerializer(required=False, allow_null=True)
|
|
@@ -238,7 +324,7 @@ class ConfigDataSerializer(serializers.Serializer):
|
|
|
238
324
|
help_text="User's DjangoConfig settings"
|
|
239
325
|
)
|
|
240
326
|
django_settings = serializers.DictField(
|
|
241
|
-
help_text="Complete Django settings (sanitized)"
|
|
327
|
+
help_text="Complete Django settings (sanitized, contains mixed types)"
|
|
242
328
|
)
|
|
243
329
|
_validation = ConfigValidationSerializer(
|
|
244
330
|
help_text="Validation result comparing serializer with actual config"
|
|
@@ -36,11 +36,16 @@ class UserStatisticsSerializer(serializers.Serializer):
|
|
|
36
36
|
superusers = serializers.IntegerField(help_text="Number of superusers")
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
class AppStatisticsDataSerializer(serializers.Serializer):
|
|
40
|
+
"""Serializer for application statistics data."""
|
|
41
|
+
|
|
42
|
+
name = serializers.CharField(help_text="Human-readable app name")
|
|
43
|
+
total_records = serializers.IntegerField(help_text="Total records count")
|
|
44
|
+
model_count = serializers.IntegerField(help_text="Number of models")
|
|
45
|
+
|
|
46
|
+
|
|
39
47
|
class AppStatisticsSerializer(serializers.Serializer):
|
|
40
48
|
"""Serializer for application-specific statistics."""
|
|
41
49
|
|
|
42
50
|
app_name = serializers.CharField(help_text="Application name")
|
|
43
|
-
statistics =
|
|
44
|
-
child=serializers.IntegerField(),
|
|
45
|
-
help_text="Application statistics"
|
|
46
|
-
)
|
|
51
|
+
statistics = AppStatisticsDataSerializer(help_text="Application statistics")
|