diff --git a/docker.env b/docker.env index 9f39fa6282..b7d3912647 100644 --- a/docker.env +++ b/docker.env @@ -7,3 +7,10 @@ SCANCODEIO_DB_HOST=db SCANCODEIO_RQ_REDIS_HOST=redis SCANCODEIO_ASYNC=True SCANCODEIO_WORKSPACE_LOCATION=/var/scancodeio/workspace/ +DEBUG=False +SECRET_KEY=supersecretkey123456789 +ALLOWED_HOSTS=* +SCANCODEIO_DB_NAME=scancodeio +SCANCODEIO_DB_USER=scancodeio +SCANCODEIO_DB_PASSWORD=scancodeio +SCANCODEIO_DB_PORT=5432 diff --git a/scancodeio/static/main.css b/scancodeio/static/main.css index a51269fcbe..18b65f5269 100644 --- a/scancodeio/static/main.css +++ b/scancodeio/static/main.css @@ -607,3 +607,134 @@ body.full-screen #resource-viewer .message-header { background-color: var(--bulma-background); border-radius: var(--bulma-radius); } + +/* Modal Search Palette Styles */ +.search-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 1000; + display: flex; + align-items: flex-start; + justify-content: center; + padding-top: 10vh; +} + +.search-modal-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(2px); +} + +.search-modal-content { + position: relative; + width: 90%; + max-width: 600px; + background: #1f2937; + border-radius: 12px; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); + overflow: hidden; + animation: modalSlideIn 0.2s ease-out; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.search-modal-header { + border-bottom: 1px solid #374151; + padding: 0; +} + +.search-modal-input { + width: 100%; + padding: 1rem 1.5rem; + font-size: 1.1rem; + border: none; + background: transparent; + color: #f3f4f6; + outline: none; +} + +.search-modal-input::placeholder { + color: #9ca3af; +} + +.search-modal-hint { + padding: 0.5rem 1.5rem; + font-size: 0.75rem; + color: #9ca3af; + border-top: 1px solid #374151; +} + +.search-modal-hint kbd { + display: inline-block; + padding: 0.1rem 0.4rem; + font-size: 0.7rem; + background: #374151; + border: 1px solid #4b5563; + border-radius: 3px; + margin: 0 0.1rem; + font-family: monospace; +} + +.search-results { + max-height: 400px; + overflow-y: auto; + background: #111827; +} + +.search-empty-state { + padding: 3rem 1.5rem; + text-align: center; + color: #6b7280; +} + +.search-result-item { + padding: 0.75rem 1.5rem; + border-bottom: 1px solid #374151; + cursor: pointer; + transition: background-color 0.1s; + color: #e5e7eb; +} + +.search-result-item:hover, +.search-result-item.selected { + background: #1f2937; +} + +.search-result-item.selected { + border-left: 3px solid #3b82f6; +} + +.search-result-path { + font-size: 0.95rem; + color: #e5e7eb; + margin-bottom: 0.25rem; +} + +.search-result-meta { + font-size: 0.8rem; + color: #9ca3af; +} + +.search-loading { + padding: 1rem 1.5rem; + text-align: center; + color: #9ca3af; + background: #111827; +} + diff --git a/scanpipe/templates/scanpipe/resource_detail.html b/scanpipe/templates/scanpipe/resource_detail.html index 58c23e0f55..34ff974ab3 100644 --- a/scanpipe/templates/scanpipe/resource_detail.html +++ b/scanpipe/templates/scanpipe/resource_detail.html @@ -22,8 +22,37 @@ {% endblock %} + + + {% endblock %} +{% block scripts %}\n + {% block scripts %} {{ detected_values|json_script:"detected_values" }} {% endblock %} \ No newline at end of file diff --git a/scanpipe/urls.py b/scanpipe/urls.py index c0d47fbb2c..2e21595322 100644 --- a/scanpipe/urls.py +++ b/scanpipe/urls.py @@ -36,6 +36,11 @@ views.codebase_resource_diff_view, name="resource_diff", ), + path( + "project//resources/search/", + views.resource_search_ajax_view, + name="resource_search_ajax", + ), path( "project//resources//", views.CodebaseResourceDetailsView.as_view(), diff --git a/scanpipe/views.py b/scanpipe/views.py index 5e657b874b..3bf9d09d98 100644 --- a/scanpipe/views.py +++ b/scanpipe/views.py @@ -2212,6 +2212,55 @@ def codebase_resource_diff_view(request, slug): return HttpResponse(html) +@conditional_login_required +def resource_search_ajax_view(request, slug): + """AJAX endpoint for searching resources within a project.""" + from django.http import JsonResponse + from django.db.models import Q + + project = get_object_or_404(Project, slug=slug) + query = request.GET.get("q", "").strip() + + if not query or len(query) < 2: + return JsonResponse({"results": []}) + + # Search by name, extension, or parts of path (OR logic) + # This allows searching for just the filename like "arch" or extension like ".py" + search_filter = ( + Q(name__icontains=query) | # Match filename + Q(extension__icontains=query) | # Match extension + Q(path__icontains=query) # Match anywhere in path + ) + + resources = ( + project.codebaseresources + .only("path", "name", "type", "size") + .filter(search_filter) + .order_by("name", "path")[:15] # Order by name for better UX + ) + + results = [] + for resource in resources: + # Format file size for display + size_display = "" + if resource.size: + if resource.size < 1024: + size_display = f"{resource.size} B" + elif resource.size < 1024 * 1024: + size_display = f"{resource.size / 1024:.1f} KB" + else: + size_display = f"{resource.size / (1024 * 1024):.1f} MB" + + results.append({ + "path": resource.path, + "name": resource.name or "", # Show filename separately + "type": resource.type or "file", + "size": size_display, + }) + + return JsonResponse({"results": results}) + + class DiscoveredPackageDetailsView( ConditionalLoginRequired, ProjectRelatedViewMixin,