createsonline 0.1.26__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.
- createsonline/__init__.py +46 -0
- createsonline/admin/__init__.py +7 -0
- createsonline/admin/content.py +526 -0
- createsonline/admin/crud.py +805 -0
- createsonline/admin/field_builder.py +559 -0
- createsonline/admin/integration.py +482 -0
- createsonline/admin/interface.py +2562 -0
- createsonline/admin/model_creator.py +513 -0
- createsonline/admin/model_manager.py +388 -0
- createsonline/admin/modern_dashboard.py +498 -0
- createsonline/admin/permissions.py +264 -0
- createsonline/admin/user_forms.py +594 -0
- createsonline/ai/__init__.py +202 -0
- createsonline/ai/fields.py +1226 -0
- createsonline/ai/orm.py +325 -0
- createsonline/ai/services.py +1244 -0
- createsonline/app.py +506 -0
- createsonline/auth/__init__.py +8 -0
- createsonline/auth/management.py +228 -0
- createsonline/auth/models.py +552 -0
- createsonline/cli/__init__.py +5 -0
- createsonline/cli/commands/__init__.py +122 -0
- createsonline/cli/commands/database.py +416 -0
- createsonline/cli/commands/info.py +173 -0
- createsonline/cli/commands/initdb.py +218 -0
- createsonline/cli/commands/project.py +545 -0
- createsonline/cli/commands/serve.py +173 -0
- createsonline/cli/commands/shell.py +93 -0
- createsonline/cli/commands/users.py +148 -0
- createsonline/cli/main.py +2041 -0
- createsonline/cli/manage.py +274 -0
- createsonline/config/__init__.py +9 -0
- createsonline/config/app.py +2577 -0
- createsonline/config/database.py +179 -0
- createsonline/config/docs.py +384 -0
- createsonline/config/errors.py +160 -0
- createsonline/config/orm.py +43 -0
- createsonline/config/request.py +93 -0
- createsonline/config/settings.py +176 -0
- createsonline/data/__init__.py +23 -0
- createsonline/data/dataframe.py +925 -0
- createsonline/data/io.py +453 -0
- createsonline/data/series.py +557 -0
- createsonline/database/__init__.py +60 -0
- createsonline/database/abstraction.py +440 -0
- createsonline/database/assistant.py +585 -0
- createsonline/database/fields.py +442 -0
- createsonline/database/migrations.py +132 -0
- createsonline/database/models.py +604 -0
- createsonline/database.py +438 -0
- createsonline/http/__init__.py +28 -0
- createsonline/http/client.py +535 -0
- createsonline/ml/__init__.py +55 -0
- createsonline/ml/classification.py +552 -0
- createsonline/ml/clustering.py +680 -0
- createsonline/ml/metrics.py +542 -0
- createsonline/ml/neural.py +560 -0
- createsonline/ml/preprocessing.py +784 -0
- createsonline/ml/regression.py +501 -0
- createsonline/performance/__init__.py +19 -0
- createsonline/performance/cache.py +444 -0
- createsonline/performance/compression.py +335 -0
- createsonline/performance/core.py +419 -0
- createsonline/project_init.py +789 -0
- createsonline/routing.py +528 -0
- createsonline/security/__init__.py +34 -0
- createsonline/security/core.py +811 -0
- createsonline/security/encryption.py +349 -0
- createsonline/server.py +295 -0
- createsonline/static/css/admin.css +263 -0
- createsonline/static/css/common.css +358 -0
- createsonline/static/css/dashboard.css +89 -0
- createsonline/static/favicon.ico +0 -0
- createsonline/static/icons/icon-128x128.png +0 -0
- createsonline/static/icons/icon-128x128.webp +0 -0
- createsonline/static/icons/icon-16x16.png +0 -0
- createsonline/static/icons/icon-16x16.webp +0 -0
- createsonline/static/icons/icon-180x180.png +0 -0
- createsonline/static/icons/icon-180x180.webp +0 -0
- createsonline/static/icons/icon-192x192.png +0 -0
- createsonline/static/icons/icon-192x192.webp +0 -0
- createsonline/static/icons/icon-256x256.png +0 -0
- createsonline/static/icons/icon-256x256.webp +0 -0
- createsonline/static/icons/icon-32x32.png +0 -0
- createsonline/static/icons/icon-32x32.webp +0 -0
- createsonline/static/icons/icon-384x384.png +0 -0
- createsonline/static/icons/icon-384x384.webp +0 -0
- createsonline/static/icons/icon-48x48.png +0 -0
- createsonline/static/icons/icon-48x48.webp +0 -0
- createsonline/static/icons/icon-512x512.png +0 -0
- createsonline/static/icons/icon-512x512.webp +0 -0
- createsonline/static/icons/icon-64x64.png +0 -0
- createsonline/static/icons/icon-64x64.webp +0 -0
- createsonline/static/image/android-chrome-192x192.png +0 -0
- createsonline/static/image/android-chrome-512x512.png +0 -0
- createsonline/static/image/apple-touch-icon.png +0 -0
- createsonline/static/image/favicon-16x16.png +0 -0
- createsonline/static/image/favicon-32x32.png +0 -0
- createsonline/static/image/favicon.ico +0 -0
- createsonline/static/image/favicon.svg +17 -0
- createsonline/static/image/icon-128x128.png +0 -0
- createsonline/static/image/icon-128x128.webp +0 -0
- createsonline/static/image/icon-16x16.png +0 -0
- createsonline/static/image/icon-16x16.webp +0 -0
- createsonline/static/image/icon-180x180.png +0 -0
- createsonline/static/image/icon-180x180.webp +0 -0
- createsonline/static/image/icon-192x192.png +0 -0
- createsonline/static/image/icon-192x192.webp +0 -0
- createsonline/static/image/icon-256x256.png +0 -0
- createsonline/static/image/icon-256x256.webp +0 -0
- createsonline/static/image/icon-32x32.png +0 -0
- createsonline/static/image/icon-32x32.webp +0 -0
- createsonline/static/image/icon-384x384.png +0 -0
- createsonline/static/image/icon-384x384.webp +0 -0
- createsonline/static/image/icon-48x48.png +0 -0
- createsonline/static/image/icon-48x48.webp +0 -0
- createsonline/static/image/icon-512x512.png +0 -0
- createsonline/static/image/icon-512x512.webp +0 -0
- createsonline/static/image/icon-64x64.png +0 -0
- createsonline/static/image/icon-64x64.webp +0 -0
- createsonline/static/image/logo-header-h100.png +0 -0
- createsonline/static/image/logo-header-h100.webp +0 -0
- createsonline/static/image/logo-header-h200@2x.png +0 -0
- createsonline/static/image/logo-header-h200@2x.webp +0 -0
- createsonline/static/image/logo.png +0 -0
- createsonline/static/js/admin.js +274 -0
- createsonline/static/site.webmanifest +35 -0
- createsonline/static/templates/admin/base.html +87 -0
- createsonline/static/templates/admin/dashboard.html +217 -0
- createsonline/static/templates/admin/model_form.html +270 -0
- createsonline/static/templates/admin/model_list.html +202 -0
- createsonline/static/test_script.js +15 -0
- createsonline/static/test_styles.css +59 -0
- createsonline/static_files.py +365 -0
- createsonline/templates/404.html +100 -0
- createsonline/templates/admin_login.html +169 -0
- createsonline/templates/base.html +102 -0
- createsonline/templates/index.html +151 -0
- createsonline/templates.py +205 -0
- createsonline/testing.py +322 -0
- createsonline/utils.py +448 -0
- createsonline/validation/__init__.py +49 -0
- createsonline/validation/fields.py +598 -0
- createsonline/validation/models.py +504 -0
- createsonline/validation/validators.py +561 -0
- createsonline/views.py +184 -0
- createsonline-0.1.26.dist-info/METADATA +46 -0
- createsonline-0.1.26.dist-info/RECORD +152 -0
- createsonline-0.1.26.dist-info/WHEEL +5 -0
- createsonline-0.1.26.dist-info/entry_points.txt +2 -0
- createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
- createsonline-0.1.26.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
console.log('🚀 CREATESONLINE v0.1.4 - Dynamic routing active!');
|
|
2
|
+
|
|
3
|
+
// Display feature announcement
|
|
4
|
+
setTimeout(() => {
|
|
5
|
+
const features = [
|
|
6
|
+
'✅ Automatic static file serving',
|
|
7
|
+
'✅ No manual route configuration',
|
|
8
|
+
'✅ Smart MIME type detection',
|
|
9
|
+
'✅ Built-in file caching',
|
|
10
|
+
'✅ Security: Path traversal protection'
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
console.log('\n🎯 Dynamic Static File Routing Features:');
|
|
14
|
+
features.forEach(feature => console.log(` ${feature}`));
|
|
15
|
+
}, 100);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
body {
|
|
2
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
3
|
+
max-width: 800px;
|
|
4
|
+
margin: 50px auto;
|
|
5
|
+
padding: 20px;
|
|
6
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
7
|
+
color: #fff;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container {
|
|
11
|
+
background: rgba(255, 255, 255, 0.1);
|
|
12
|
+
backdrop-filter: blur(10px);
|
|
13
|
+
border-radius: 20px;
|
|
14
|
+
padding: 40px;
|
|
15
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
h1 {
|
|
19
|
+
font-size: 2.5em;
|
|
20
|
+
margin-bottom: 20px;
|
|
21
|
+
text-align: center;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
h2 {
|
|
25
|
+
color: #ffd700;
|
|
26
|
+
margin-top: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.feature-box {
|
|
30
|
+
background: rgba(255, 255, 255, 0.05);
|
|
31
|
+
border-left: 4px solid #ffd700;
|
|
32
|
+
padding: 20px;
|
|
33
|
+
margin-top: 30px;
|
|
34
|
+
border-radius: 10px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
ul {
|
|
38
|
+
list-style: none;
|
|
39
|
+
padding-left: 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
li {
|
|
43
|
+
padding: 8px 0;
|
|
44
|
+
padding-left: 30px;
|
|
45
|
+
position: relative;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
li::before {
|
|
49
|
+
content: "âœ"";
|
|
50
|
+
position: absolute;
|
|
51
|
+
left: 0;
|
|
52
|
+
color: #4ade80;
|
|
53
|
+
font-weight: bold;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
p {
|
|
57
|
+
font-size: 1.2em;
|
|
58
|
+
line-height: 1.6;
|
|
59
|
+
}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# createsonline/static_files.py
|
|
2
|
+
"""
|
|
3
|
+
CREATESONLINE Dynamic Static File Serving System
|
|
4
|
+
|
|
5
|
+
Automatically serves HTML, CSS, JS, and other static files
|
|
6
|
+
with intelligent MIME type detection and caching.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import mimetypes
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Dict, Optional, Tuple
|
|
13
|
+
import logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("createsonline.static")
|
|
16
|
+
|
|
17
|
+
# Initialize mimetypes
|
|
18
|
+
mimetypes.init()
|
|
19
|
+
|
|
20
|
+
# Additional MIME types for modern web
|
|
21
|
+
CUSTOM_MIME_TYPES = {
|
|
22
|
+
'.html': 'text/html',
|
|
23
|
+
'.htm': 'text/html',
|
|
24
|
+
'.css': 'text/css',
|
|
25
|
+
'.js': 'application/javascript',
|
|
26
|
+
'.json': 'application/json',
|
|
27
|
+
'.xml': 'application/xml',
|
|
28
|
+
'.svg': 'image/svg+xml',
|
|
29
|
+
'.woff': 'font/woff',
|
|
30
|
+
'.woff2': 'font/woff2',
|
|
31
|
+
'.ttf': 'font/ttf',
|
|
32
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
33
|
+
'.png': 'image/png',
|
|
34
|
+
'.jpg': 'image/jpeg',
|
|
35
|
+
'.jpeg': 'image/jpeg',
|
|
36
|
+
'.gif': 'image/gif',
|
|
37
|
+
'.ico': 'image/x-icon',
|
|
38
|
+
'.webp': 'image/webp',
|
|
39
|
+
'.mp4': 'video/mp4',
|
|
40
|
+
'.webm': 'video/webm',
|
|
41
|
+
'.mp3': 'audio/mpeg',
|
|
42
|
+
'.wav': 'audio/wav',
|
|
43
|
+
'.pdf': 'application/pdf',
|
|
44
|
+
'.zip': 'application/zip',
|
|
45
|
+
'.txt': 'text/plain',
|
|
46
|
+
'.md': 'text/markdown',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class StaticFileHandler:
|
|
51
|
+
"""
|
|
52
|
+
Dynamic static file serving with intelligent routing
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
def __init__(self, static_dirs: list = None, template_dirs: list = None):
|
|
56
|
+
"""
|
|
57
|
+
Initialize static file handler
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
static_dirs: List of directories to serve static files from
|
|
61
|
+
template_dirs: List of directories to serve templates/HTML from
|
|
62
|
+
"""
|
|
63
|
+
self._static_dirs = static_dirs
|
|
64
|
+
self._template_dirs = template_dirs
|
|
65
|
+
|
|
66
|
+
# Cache for file existence checks
|
|
67
|
+
self._file_cache = {}
|
|
68
|
+
|
|
69
|
+
logger.info("Static file handler initialized")
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def static_dirs(self):
|
|
73
|
+
"""Lazily evaluate static directories using Django-style settings"""
|
|
74
|
+
if self._static_dirs is not None:
|
|
75
|
+
return self._static_dirs
|
|
76
|
+
|
|
77
|
+
# Import settings here to avoid circular imports
|
|
78
|
+
from .config.settings import settings
|
|
79
|
+
|
|
80
|
+
# Use STATICFILES_DIRS from settings (Django-style)
|
|
81
|
+
staticfiles_dirs = settings.STATICFILES_DIRS
|
|
82
|
+
|
|
83
|
+
if staticfiles_dirs:
|
|
84
|
+
# Add subdirectories for better organization
|
|
85
|
+
dirs = []
|
|
86
|
+
for base_dir in staticfiles_dirs:
|
|
87
|
+
dirs.append(base_dir)
|
|
88
|
+
# Also add common subdirectories
|
|
89
|
+
for subdir in ['css', 'js', 'images', 'img', 'icons']:
|
|
90
|
+
sub_path = base_dir / subdir
|
|
91
|
+
if sub_path.exists():
|
|
92
|
+
dirs.append(sub_path)
|
|
93
|
+
return dirs
|
|
94
|
+
|
|
95
|
+
# Fallback to current directory (backward compatibility)
|
|
96
|
+
cwd = Path.cwd()
|
|
97
|
+
dirs = [
|
|
98
|
+
cwd / "static",
|
|
99
|
+
cwd / "static" / "css",
|
|
100
|
+
cwd / "static" / "js",
|
|
101
|
+
cwd / "static" / "images",
|
|
102
|
+
cwd / "static" / "icons",
|
|
103
|
+
]
|
|
104
|
+
return dirs
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def template_dirs(self):
|
|
108
|
+
"""Lazily evaluate template directories using Django-style settings"""
|
|
109
|
+
if self._template_dirs is not None:
|
|
110
|
+
return self._template_dirs
|
|
111
|
+
|
|
112
|
+
# Import settings here to avoid circular imports
|
|
113
|
+
from .config.settings import settings
|
|
114
|
+
|
|
115
|
+
# Use TEMPLATE_DIRS from settings
|
|
116
|
+
template_dirs = settings.TEMPLATE_DIRS
|
|
117
|
+
|
|
118
|
+
if template_dirs:
|
|
119
|
+
return template_dirs
|
|
120
|
+
|
|
121
|
+
# Fallback
|
|
122
|
+
cwd = Path.cwd()
|
|
123
|
+
return [cwd / "templates"]
|
|
124
|
+
|
|
125
|
+
def get_mime_type(self, file_path: str) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Get MIME type for a file with fallback support
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
file_path: Path to the file
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
MIME type string
|
|
134
|
+
"""
|
|
135
|
+
ext = os.path.splitext(file_path)[1].lower()
|
|
136
|
+
|
|
137
|
+
# Check custom types first
|
|
138
|
+
if ext in CUSTOM_MIME_TYPES:
|
|
139
|
+
return CUSTOM_MIME_TYPES[ext]
|
|
140
|
+
|
|
141
|
+
# Fall back to system mimetypes
|
|
142
|
+
mime_type, _ = mimetypes.guess_type(file_path)
|
|
143
|
+
|
|
144
|
+
# Default to octet-stream if unknown
|
|
145
|
+
return mime_type or 'application/octet-stream'
|
|
146
|
+
|
|
147
|
+
def find_file(self, relative_path: str) -> Optional[Path]:
|
|
148
|
+
"""
|
|
149
|
+
Find a file in static or template directories
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
relative_path: Relative path to the file
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Absolute Path object if found, None otherwise
|
|
156
|
+
"""
|
|
157
|
+
# Check cache first
|
|
158
|
+
if relative_path in self._file_cache:
|
|
159
|
+
cached_path = self._file_cache[relative_path]
|
|
160
|
+
if cached_path and cached_path.exists():
|
|
161
|
+
return cached_path
|
|
162
|
+
|
|
163
|
+
# Remove leading slash if present
|
|
164
|
+
relative_path = relative_path.lstrip('/')
|
|
165
|
+
|
|
166
|
+
# Strip /static/ prefix if present (URLs come as /static/css/file.css)
|
|
167
|
+
# but we want to search in static_dirs which already points to static/
|
|
168
|
+
search_path = relative_path
|
|
169
|
+
if search_path.startswith('static/'):
|
|
170
|
+
search_path = search_path[7:] # Remove 'static/' prefix
|
|
171
|
+
logger.debug(f"Stripped 'static/' prefix: '{relative_path}' -> '{search_path}'")
|
|
172
|
+
|
|
173
|
+
# PRIORITY 1: Check project root for root-level static files
|
|
174
|
+
# (favicon.svg, logo.png, site.webmanifest, robots.txt, etc.)
|
|
175
|
+
project_root = Path.cwd()
|
|
176
|
+
root_file = project_root / search_path
|
|
177
|
+
if root_file.exists() and root_file.is_file():
|
|
178
|
+
# Security check: ensure it's a known static file type
|
|
179
|
+
static_extensions = {'.svg', '.ico', '.png', '.jpg', '.jpeg', '.gif',
|
|
180
|
+
'.webp', '.webmanifest', '.json', '.txt', '.xml'}
|
|
181
|
+
if root_file.suffix.lower() in static_extensions:
|
|
182
|
+
self._file_cache[relative_path] = root_file
|
|
183
|
+
logger.debug(f"Found root-level file: {root_file}")
|
|
184
|
+
return root_file
|
|
185
|
+
|
|
186
|
+
# PRIORITY 2: Check static directories
|
|
187
|
+
for static_dir in self.static_dirs:
|
|
188
|
+
file_path = Path(static_dir) / search_path
|
|
189
|
+
logger.debug(f"Checking: {file_path} (exists={file_path.exists()})")
|
|
190
|
+
if file_path.exists() and file_path.is_file():
|
|
191
|
+
self._file_cache[relative_path] = file_path
|
|
192
|
+
logger.debug(f"Found file: {file_path}")
|
|
193
|
+
return file_path
|
|
194
|
+
|
|
195
|
+
# PRIORITY 3: Check template directories
|
|
196
|
+
for template_dir in self.template_dirs:
|
|
197
|
+
file_path = Path(template_dir) / search_path
|
|
198
|
+
if file_path.exists() and file_path.is_file():
|
|
199
|
+
self._file_cache[relative_path] = file_path
|
|
200
|
+
return file_path
|
|
201
|
+
|
|
202
|
+
# Cache miss
|
|
203
|
+
self._file_cache[relative_path] = None
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
def serve_file(self, relative_path: str) -> Tuple[bytes, int, Dict[str, str]]:
|
|
207
|
+
"""
|
|
208
|
+
Serve a static file
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
relative_path: Relative path to the file
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Tuple of (content, status_code, headers)
|
|
215
|
+
"""
|
|
216
|
+
file_path = self.find_file(relative_path)
|
|
217
|
+
|
|
218
|
+
if not file_path:
|
|
219
|
+
error_content = f"File not found: {relative_path}".encode()
|
|
220
|
+
return error_content, 404, {'Content-Type': 'text/plain'}
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
# Security check - ensure file is within allowed directories
|
|
224
|
+
resolved_path = file_path.resolve()
|
|
225
|
+
allowed = False
|
|
226
|
+
|
|
227
|
+
# Allow project root for root-level static files
|
|
228
|
+
project_root = Path.cwd().resolve()
|
|
229
|
+
if str(resolved_path).startswith(str(project_root)):
|
|
230
|
+
# Check if it's a known static file type
|
|
231
|
+
static_extensions = {'.svg', '.ico', '.png', '.jpg', '.jpeg', '.gif',
|
|
232
|
+
'.webp', '.webmanifest', '.json', '.txt', '.xml',
|
|
233
|
+
'.css', '.js', '.woff', '.woff2', '.ttf'}
|
|
234
|
+
if resolved_path.suffix.lower() in static_extensions:
|
|
235
|
+
allowed = True
|
|
236
|
+
|
|
237
|
+
# Also check configured static/template directories
|
|
238
|
+
if not allowed:
|
|
239
|
+
for static_dir in self.static_dirs + self.template_dirs:
|
|
240
|
+
if str(resolved_path).startswith(str(Path(static_dir).resolve())):
|
|
241
|
+
allowed = True
|
|
242
|
+
break
|
|
243
|
+
|
|
244
|
+
if not allowed:
|
|
245
|
+
error_content = b"Access denied"
|
|
246
|
+
return error_content, 403, {'Content-Type': 'text/plain'}
|
|
247
|
+
|
|
248
|
+
# Read file content
|
|
249
|
+
with open(file_path, 'rb') as f:
|
|
250
|
+
content = f.read()
|
|
251
|
+
|
|
252
|
+
# Get MIME type
|
|
253
|
+
mime_type = self.get_mime_type(str(file_path))
|
|
254
|
+
|
|
255
|
+
# Prepare headers
|
|
256
|
+
headers = {
|
|
257
|
+
'Content-Type': mime_type,
|
|
258
|
+
'Content-Length': str(len(content)),
|
|
259
|
+
'Cache-Control': 'public, max-age=3600', # Cache for 1 hour
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
# Add additional headers for specific file types
|
|
263
|
+
if mime_type.startswith('text/'):
|
|
264
|
+
headers['Content-Type'] = f"{mime_type}; charset=utf-8"
|
|
265
|
+
|
|
266
|
+
logger.debug(f"Serving file: {relative_path} ({mime_type}, {len(content)} bytes)")
|
|
267
|
+
|
|
268
|
+
return content, 200, headers
|
|
269
|
+
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.error(f"Error serving file {relative_path}: {e}")
|
|
272
|
+
error_content = f"Error reading file: {str(e)}".encode()
|
|
273
|
+
return error_content, 500, {'Content-Type': 'text/plain'}
|
|
274
|
+
|
|
275
|
+
def list_files(self, directory: str = "") -> Dict[str, list]:
|
|
276
|
+
"""
|
|
277
|
+
List all files in static directories (for debugging)
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
directory: Subdirectory to list (empty for all)
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Dictionary with file listings
|
|
284
|
+
"""
|
|
285
|
+
files = {
|
|
286
|
+
'static': [],
|
|
287
|
+
'templates': []
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
# List static files
|
|
291
|
+
for static_dir in self.static_dirs:
|
|
292
|
+
base_dir = Path(static_dir) / directory
|
|
293
|
+
if base_dir.exists():
|
|
294
|
+
for file_path in base_dir.rglob('*'):
|
|
295
|
+
if file_path.is_file():
|
|
296
|
+
rel_path = file_path.relative_to(static_dir)
|
|
297
|
+
files['static'].append(str(rel_path))
|
|
298
|
+
|
|
299
|
+
# List template files
|
|
300
|
+
for template_dir in self.template_dirs:
|
|
301
|
+
base_dir = Path(template_dir) / directory
|
|
302
|
+
if base_dir.exists():
|
|
303
|
+
for file_path in base_dir.rglob('*'):
|
|
304
|
+
if file_path.is_file():
|
|
305
|
+
rel_path = file_path.relative_to(template_dir)
|
|
306
|
+
files['templates'].append(str(rel_path))
|
|
307
|
+
|
|
308
|
+
return files
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# Global static file handler instance
|
|
312
|
+
static_handler = StaticFileHandler()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def configure_static_handler(static_dirs: list = None, template_dirs: list = None):
|
|
316
|
+
"""
|
|
317
|
+
Manually configure the global static handler (optional).
|
|
318
|
+
|
|
319
|
+
By default, the handler auto-discovers directories using Django-style settings.
|
|
320
|
+
Only use this if you need to override the auto-discovery.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
static_dirs: List of static file directories
|
|
324
|
+
template_dirs: List of template directories
|
|
325
|
+
"""
|
|
326
|
+
global static_handler
|
|
327
|
+
|
|
328
|
+
if static_dirs:
|
|
329
|
+
print(f"[DEBUG] Manually configuring static_handler with: {[str(d) for d in static_dirs]}")
|
|
330
|
+
static_handler._static_dirs = static_dirs
|
|
331
|
+
|
|
332
|
+
if template_dirs:
|
|
333
|
+
static_handler._template_dirs = template_dirs
|
|
334
|
+
|
|
335
|
+
static_handler._file_cache = {} # Clear cache
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def serve_static(path: str) -> Tuple[bytes, int, Dict[str, str]]:
|
|
339
|
+
"""
|
|
340
|
+
Convenience function to serve static files
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
path: Path to the file
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
Tuple of (content, status_code, headers)
|
|
347
|
+
"""
|
|
348
|
+
return static_handler.serve_file(path)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def serve_template(path: str) -> Tuple[bytes, int, Dict[str, str]]:
|
|
352
|
+
"""
|
|
353
|
+
Convenience function to serve HTML templates
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
path: Path to the template
|
|
357
|
+
|
|
358
|
+
Returns:
|
|
359
|
+
Tuple of (content, status_code, headers)
|
|
360
|
+
"""
|
|
361
|
+
# Ensure .html extension
|
|
362
|
+
if not path.endswith('.html'):
|
|
363
|
+
path = f"{path}.html"
|
|
364
|
+
|
|
365
|
+
return static_handler.serve_file(path)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>404 - Page Not Found</title>
|
|
7
|
+
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
9
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
* {
|
|
13
|
+
margin: 0;
|
|
14
|
+
padding: 0;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
body {
|
|
19
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
|
|
20
|
+
background: #0a0a0a;
|
|
21
|
+
color: #ffffff;
|
|
22
|
+
min-height: 100vh;
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
justify-content: center;
|
|
26
|
+
padding: 20px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.container {
|
|
30
|
+
max-width: 600px;
|
|
31
|
+
width: 100%;
|
|
32
|
+
text-align: center;
|
|
33
|
+
background: #1a1a1a;
|
|
34
|
+
padding: 60px 40px;
|
|
35
|
+
border-radius: 16px;
|
|
36
|
+
border: 1px solid #2a2a2a;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.error-code {
|
|
40
|
+
font-size: 8em;
|
|
41
|
+
font-weight: 700;
|
|
42
|
+
background: linear-gradient(135deg, #ffffff 0%, #666666 100%);
|
|
43
|
+
-webkit-background-clip: text;
|
|
44
|
+
-webkit-text-fill-color: transparent;
|
|
45
|
+
background-clip: text;
|
|
46
|
+
margin-bottom: 20px;
|
|
47
|
+
line-height: 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
h1 {
|
|
51
|
+
font-size: 2em;
|
|
52
|
+
margin-bottom: 15px;
|
|
53
|
+
color: #ffffff;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
p {
|
|
57
|
+
color: #999999;
|
|
58
|
+
font-size: 1.1em;
|
|
59
|
+
margin-bottom: 40px;
|
|
60
|
+
line-height: 1.6;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.btn {
|
|
64
|
+
display: inline-block;
|
|
65
|
+
padding: 15px 35px;
|
|
66
|
+
background: #ffffff;
|
|
67
|
+
color: #000000;
|
|
68
|
+
text-decoration: none;
|
|
69
|
+
border-radius: 8px;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
transition: all 0.3s ease;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.btn:hover {
|
|
75
|
+
background: #f0f0f0;
|
|
76
|
+
transform: translateY(-2px);
|
|
77
|
+
box-shadow: 0 10px 25px rgba(255, 255, 255, 0.2);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@media (max-width: 600px) {
|
|
81
|
+
.error-code {
|
|
82
|
+
font-size: 5em;
|
|
83
|
+
}
|
|
84
|
+
h1 {
|
|
85
|
+
font-size: 1.5em;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
</style>
|
|
89
|
+
</head>
|
|
90
|
+
<body>
|
|
91
|
+
<div class="container">
|
|
92
|
+
<div class="error-code">404</div>
|
|
93
|
+
<h1>Page Not Found</h1>
|
|
94
|
+
<p>
|
|
95
|
+
The page you're looking for doesn't exist or has been moved.
|
|
96
|
+
</p>
|
|
97
|
+
<a href="/" class="btn">Go Home</a>
|
|
98
|
+
</div>
|
|
99
|
+
</body>
|
|
100
|
+
</html>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}Admin Login - CREATESONLINE{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block extra_style %}
|
|
6
|
+
h1 {
|
|
7
|
+
font-size: 2em;
|
|
8
|
+
color: #000000;
|
|
9
|
+
margin-bottom: 10px;
|
|
10
|
+
text-align: center;
|
|
11
|
+
font-weight: 300;
|
|
12
|
+
letter-spacing: -0.5px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
h1::after {
|
|
16
|
+
content: '';
|
|
17
|
+
display: block;
|
|
18
|
+
width: 40px;
|
|
19
|
+
height: 2px;
|
|
20
|
+
background: #000000;
|
|
21
|
+
margin: 15px auto 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.subtitle {
|
|
25
|
+
text-align: center;
|
|
26
|
+
color: #666666;
|
|
27
|
+
font-size: 0.95em;
|
|
28
|
+
margin-bottom: 45px;
|
|
29
|
+
font-weight: 300;
|
|
30
|
+
line-height: 1.6;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.form-group {
|
|
34
|
+
margin-bottom: 30px;
|
|
35
|
+
position: relative;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
label {
|
|
39
|
+
display: block;
|
|
40
|
+
color: #000000;
|
|
41
|
+
font-size: 0.85em;
|
|
42
|
+
margin-bottom: 10px;
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
text-transform: uppercase;
|
|
45
|
+
letter-spacing: 1px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
input[type="text"],
|
|
49
|
+
input[type="password"] {
|
|
50
|
+
width: 100%;
|
|
51
|
+
padding: 15px 18px;
|
|
52
|
+
border: 2px solid #e0e0e0;
|
|
53
|
+
background: #ffffff;
|
|
54
|
+
font-size: 1em;
|
|
55
|
+
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
56
|
+
font-family: inherit;
|
|
57
|
+
color: #000000;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
input[type="text"]:focus,
|
|
61
|
+
input[type="password"]:focus {
|
|
62
|
+
outline: none;
|
|
63
|
+
background: #fafafa;
|
|
64
|
+
border-color: #000000;
|
|
65
|
+
transform: translateY(-2px);
|
|
66
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
button {
|
|
70
|
+
width: 100%;
|
|
71
|
+
padding: 18px;
|
|
72
|
+
background: #000000;
|
|
73
|
+
color: white;
|
|
74
|
+
border: 2px solid #000000;
|
|
75
|
+
font-size: 1em;
|
|
76
|
+
font-weight: 600;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
|
|
79
|
+
text-transform: uppercase;
|
|
80
|
+
letter-spacing: 1.5px;
|
|
81
|
+
font-family: inherit;
|
|
82
|
+
margin-top: 10px;
|
|
83
|
+
position: relative;
|
|
84
|
+
overflow: hidden;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
button::before {
|
|
88
|
+
content: '';
|
|
89
|
+
position: absolute;
|
|
90
|
+
top: 0;
|
|
91
|
+
left: -100%;
|
|
92
|
+
width: 100%;
|
|
93
|
+
height: 100%;
|
|
94
|
+
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
|
95
|
+
transition: left 0.5s;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
button:hover::before {
|
|
99
|
+
left: 100%;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
button:hover {
|
|
103
|
+
background: #ffffff;
|
|
104
|
+
color: #000000;
|
|
105
|
+
transform: translateY(-2px);
|
|
106
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
button:active {
|
|
110
|
+
transform: translateY(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.back-link {
|
|
114
|
+
text-align: center;
|
|
115
|
+
margin-top: 35px;
|
|
116
|
+
padding-top: 30px;
|
|
117
|
+
border-top: 1px solid #e0e0e0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.back-link a {
|
|
121
|
+
color: #666666;
|
|
122
|
+
text-decoration: none;
|
|
123
|
+
font-size: 0.9em;
|
|
124
|
+
transition: all 0.3s ease;
|
|
125
|
+
font-weight: 300;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.back-link a:hover {
|
|
129
|
+
color: #000000;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.container {
|
|
133
|
+
max-width: 480px;
|
|
134
|
+
padding: 60px 50px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@media (max-width: 480px) {
|
|
138
|
+
.container {
|
|
139
|
+
padding: 40px 30px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
h1 {
|
|
143
|
+
font-size: 1.6em;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
{% endblock %}
|
|
147
|
+
|
|
148
|
+
{% block content %}
|
|
149
|
+
<h1>Admin Login</h1>
|
|
150
|
+
<p class="subtitle">Enter your credentials to access the admin panel</p>
|
|
151
|
+
|
|
152
|
+
<form method="POST" action="/admin/login">
|
|
153
|
+
<div class="form-group">
|
|
154
|
+
<label for="username">Username</label>
|
|
155
|
+
<input type="text" id="username" name="username" required>
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
<div class="form-group">
|
|
159
|
+
<label for="password">Password</label>
|
|
160
|
+
<input type="password" id="password" name="password" required>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<button type="submit">Sign In</button>
|
|
164
|
+
</form>
|
|
165
|
+
|
|
166
|
+
<div class="back-link">
|
|
167
|
+
<a href="/">Back to Home</a>
|
|
168
|
+
</div>
|
|
169
|
+
{% endblock %}
|