diff --git a/gateway/config/settings/local.py b/gateway/config/settings/local.py index 9ce09379d..78458464c 100644 --- a/gateway/config/settings/local.py +++ b/gateway/config/settings/local.py @@ -59,6 +59,14 @@ # http://whitenoise.evans.io/en/latest/django.html#using-whitenoise-in-development INSTALLED_APPS: list[str] = ["whitenoise.runserver_nostatic", *INSTALLED_APPS] +# TEMPLATES +# ------------------------------------------------------------------------------ +# https://docs.djangoproject.com/en/latest/topics/templates/#module-django.template.backends.django +# 'debug': a boolean that turns on/off template debug mode. +# If it is True, the fancy error page will display a detailed report for any exception +# raised during template rendering. This report contains the relevant snippet of the +# template with the appropriate line highlighted. +TEMPLATES[0]["OPTIONS"]["debug"] = True # DJANGO-DEBUG-TOOLBAR # ------------------------------------------------------------------------------ @@ -76,6 +84,7 @@ ], "SHOW_TEMPLATE_CONTEXT": True, } + # https://django-debug-toolbar.readthedocs.io/en/latest/installation.html#internal-ips INTERNAL_IPS: list[str] = ["127.0.0.1", "10.0.2.2"] if env("USE_DOCKER") == "yes": diff --git a/gateway/justfile b/gateway/justfile index 7a5423d8f..b7a6bd5c3 100644 --- a/gateway/justfile +++ b/gateway/justfile @@ -179,7 +179,7 @@ logs-once *args: [group('service')] down *args: @echo "Stopping sds-gateway" - {{ docker_compose }} down {{ args }} + {{ docker_compose }} down --remove-orphans {{ args }} # runs the pre-commit hooks with dev dependencies [group('development')] @@ -259,6 +259,8 @@ up *args: echo "Starting sds-gateway in detached mode" echo "Environment: '{{ env }}'" echo "Compose file: '{{ compose_file }}'" + echo "Tip: call \`just build\` before \`just up\` to prevent " + echo " Docker from trying to pull a locally built image from the registry" {{ docker_compose }} up --detach --remove-orphans {{ args }} # this watcher is not working as intended today, and diff --git a/gateway/sds_gateway/api_methods/serializers/dataset_serializers.py b/gateway/sds_gateway/api_methods/serializers/dataset_serializers.py index 1831585c4..7c58fb97a 100644 --- a/gateway/sds_gateway/api_methods/serializers/dataset_serializers.py +++ b/gateway/sds_gateway/api_methods/serializers/dataset_serializers.py @@ -7,11 +7,15 @@ from sds_gateway.api_methods.models import PermissionLevel from sds_gateway.api_methods.models import UserSharePermission +READABLE_ISO_DATE_TIME: str = "%Y-%m-%d %H:%M:%S%z" + class DatasetGetSerializer(serializers.ModelSerializer[Dataset]): authors = serializers.SerializerMethodField() keywords = serializers.SerializerMethodField() - created_at = serializers.DateTimeField(format="%m/%d/%Y %H:%M:%S", read_only=True) + created_at = serializers.DateTimeField( + format=READABLE_ISO_DATE_TIME, read_only=True + ) is_shared_with_me = serializers.SerializerMethodField() is_owner = serializers.SerializerMethodField() status_display = serializers.CharField(source="get_status_display", read_only=True) @@ -165,3 +169,80 @@ def get_can_edit(self, obj): class Meta: model = Dataset fields = "__all__" + + +class DatasetPublicSerializer(serializers.ModelSerializer[Dataset]): + """ + Serializer for public dataset access (unauthenticated or public datasets). + + This serializer uses an explicit allowlist of fields to avoid exposing + sensitive internal metadata like shared_with user IDs or owner information. + """ + + authors = serializers.SerializerMethodField() + keywords = serializers.SerializerMethodField() + created_at = serializers.DateTimeField( + format=READABLE_ISO_DATE_TIME, read_only=True + ) + status_display = serializers.CharField(source="get_status_display", read_only=True) + owner_name = serializers.SerializerMethodField() + + def get_authors(self, obj): + """Return the full authors list using the model's get_authors_display method.""" + return obj.get_authors_display() + + def get_keywords(self, obj): + """Return a list of keyword names for the dataset.""" + return [kw.name for kw in obj.keywords.filter(is_deleted=False)] + + def get_owner_name(self, obj): + """Get the owner's display name.""" + return obj.owner.name if obj.owner else "Owner" + + class Meta: + model = Dataset + fields = [ + "uuid", + "name", + "status", + "status_display", + "abstract", + "description", + "doi", + "authors", + "license", + "keywords", + "institutions", + "release_date", + "repository", + "version", + "website", + "provenance", + "citation", + "other", + "created_at", + "is_public", + "owner_name", + ] + + +def get_dataset_serializer(dataset: Dataset, *, has_user_access: bool) -> dict: # pyright: ignore[reportMissingTypeArgument] + """ + Get serialized dataset data using the appropriate serializer. + + Args: + dataset: The dataset to serialize + has_user_access: Whether the requesting user has authenticated access + + Returns: + Serialized dataset data as a dictionary + + Notes: + - Uses DatasetGetSerializer for authenticated users with access + (includes sharing info, permissions, etc.) + - Uses DatasetPublicSerializer for unauthenticated/public-only access + (excludes sensitive fields like shared_with user IDs) + """ + if has_user_access: + return DatasetGetSerializer(dataset).data + return DatasetPublicSerializer(dataset).data diff --git a/gateway/sds_gateway/api_methods/tasks.py b/gateway/sds_gateway/api_methods/tasks.py index db7521636..e4aed2651 100644 --- a/gateway/sds_gateway/api_methods/tasks.py +++ b/gateway/sds_gateway/api_methods/tasks.py @@ -573,7 +573,7 @@ def notify_shared_users( if item_type == "dataset": item_url = f"{settings.SITE_URL}/users/dataset-list/" elif item_type == "capture": - item_url = f"{settings.SITE_URL}/users/file-list/" + item_url = f"{settings.SITE_URL}/users/capture-list/" else: item_url = settings.SITE_URL diff --git a/gateway/sds_gateway/static/css/file-manager.css b/gateway/sds_gateway/static/css/file-manager.css index 8d519b5c5..100dd5288 100644 --- a/gateway/sds_gateway/static/css/file-manager.css +++ b/gateway/sds_gateway/static/css/file-manager.css @@ -34,12 +34,6 @@ --z-index-sticky: 10; } -/* Base Styles */ -/* Apply Google Sans font only to file manager content */ -.files-page-container { - font-family: var(--font-family); -} - /* Accessibility */ /* Focus styles for keyboard navigation */ .file-card:focus-visible, @@ -70,8 +64,10 @@ display: flex; flex-direction: column; height: auto; - min-height: 200px; /* Minimum height to prevent empty state from looking too small */ - max-height: calc(100vh - 250px); /* Maximum height based on viewport */ + min-height: 200px; + /* Minimum height to prevent empty state from looking too small */ + max-height: calc(100vh - 250px); + /* Maximum height based on viewport */ overflow: auto; } @@ -84,8 +80,10 @@ border: 1px solid var(--color-border); border-radius: 8px; margin: 0 16px; - height: auto; /* Let it grow based on content */ - min-height: 100px; /* Minimum height for empty state */ + height: auto; + /* Let it grow based on content */ + min-height: 100px; + /* Minimum height for empty state */ } /* Individual file card */ @@ -163,13 +161,15 @@ .file-card:hover .file-name, .file-card:hover .folder-icon, .file-card:hover .file-icon { - color: #005a9c; /* SpectrumX blue */ + color: #005a9c; + /* SpectrumX blue */ } /* No hover effects for H5 files */ .file-card[data-type="file"]:not([data-h5-file="true"]):hover .file-name, .file-card[data-type="file"]:not([data-h5-file="true"]):hover .file-icon { - color: #005a9c; /* SpectrumX blue */ + color: #005a9c; + /* SpectrumX blue */ } /* Directory hover should also use the same blue */ @@ -182,13 +182,16 @@ color: var(--color-text-secondary); white-space: nowrap; flex-shrink: 0; - min-width: 150px; /* Increased width for modified date */ - text-align: center; /* Center align the modified date */ + min-width: 150px; + /* Increased width for modified date */ + text-align: center; + /* Center align the modified date */ display: flex; align-items: center; justify-content: center; position: relative; - padding-right: 24px; /* reserve space for right-aligned shared icon */ + padding-right: 24px; + /* reserve space for right-aligned shared icon */ } /* Keep the shared-with icon from shifting the centered date */ @@ -198,9 +201,12 @@ color: var(--color-text-secondary); white-space: nowrap; flex-shrink: 0; - min-width: 120px; /* Fixed width for shared by column */ - text-align: center; /* Center align shared by */ - margin-left: 0; /* Remove offset that pushes content */ + min-width: 120px; + /* Fixed width for shared by column */ + text-align: center; + /* Center align shared by */ + margin-left: 0; + /* Remove offset that pushes content */ display: flex; align-items: center; justify-content: center; @@ -210,8 +216,10 @@ font-size: 0.8125rem; white-space: nowrap; flex-shrink: 0; - min-width: 80px; /* Fixed width for actions column */ - text-align: center; /* Center align actions */ + min-width: 80px; + /* Fixed width for actions column */ + text-align: center; + /* Center align actions */ display: flex; align-items: center; justify-content: center; @@ -268,7 +276,8 @@ font-size: 0.875rem; height: var(--breadcrumb-height); font-family: var(--font-family); - font-weight: 400; /* Add normal font weight */ + font-weight: 400; + /* Add normal font weight */ } .breadcrumb-item { @@ -281,7 +290,8 @@ padding: 0 4px; border-radius: 4px; font-family: var(--font-family); - font-weight: 400; /* Add normal font weight */ + font-weight: 400; + /* Add normal font weight */ } .breadcrumb-item:hover { @@ -293,7 +303,8 @@ text-decoration: none; padding: 0 4px; font-family: var(--font-family); - font-weight: 400; /* Add normal font weight */ + font-weight: 400; + /* Add normal font weight */ } .breadcrumb-item:hover a { @@ -383,7 +394,8 @@ list-style: none; padding: 0; margin: 0; - max-height: var(--file-list-max-height); /* taller list for large folder previews */ + max-height: var(--file-list-max-height); + /* taller list for large folder previews */ overflow-y: auto; border: 1px solid #dee2e6; border-radius: 4px; @@ -771,9 +783,11 @@ /* Actions Bar (scoped) */ .files-actions-bar { - margin: 24px 16px; /* Keep the margin */ + margin: 24px 16px; + /* Keep the margin */ display: flex; - justify-content: flex-start; /* Align to the left */ + justify-content: flex-start; + /* Align to the left */ align-items: center; } @@ -793,8 +807,10 @@ align-items: center; gap: 8px; transition: var(--transition-default); - min-width: 100px; /* Give the button a minimum width */ - justify-content: center; /* Center the button content */ + min-width: 100px; + /* Give the button a minimum width */ + justify-content: center; + /* Center the button content */ } .new-button:hover { @@ -860,6 +876,7 @@ .files-actions-bar { margin: 16px 8px; } + .files-grid { margin: 0 8px; } @@ -869,6 +886,7 @@ padding: 8px 12px; gap: 8px; } + .file-name { padding-right: 0; } @@ -878,6 +896,7 @@ .file-shared { display: none; } + .file-card.header .file-meta, .file-card.header .file-shared { display: none; @@ -887,9 +906,11 @@ .upload-zone { padding: 1rem; } + .upload-zone-icon { font-size: 2.4rem; } + .selected-files-list { max-height: 220px; } @@ -900,6 +921,7 @@ height: auto; row-gap: 4px; } + .breadcrumb-item { height: auto; } @@ -911,9 +933,11 @@ .modal-footer { padding: 12px 16px; } + .modal-body { padding: 16px; } + .modal-dialog { margin: 0.5rem; } diff --git a/gateway/sds_gateway/static/js/deprecated/userSearchComponent.js b/gateway/sds_gateway/static/js/deprecated/userSearchComponent.js index cc3215b2d..3832439ad 100644 --- a/gateway/sds_gateway/static/js/deprecated/userSearchComponent.js +++ b/gateway/sds_gateway/static/js/deprecated/userSearchComponent.js @@ -1095,7 +1095,7 @@ class UserSearchHandler { if (this.itemType === "dataset") { refreshUrl = `/users/dataset-list/?page=${currentPage}&sort_by=${sortBy}&sort_order=${sortOrder}`; } else if (this.itemType === "capture") { - refreshUrl = `/users/file-list/?page=${currentPage}&sort_by=${sortBy}&sort_order=${sortOrder}`; + refreshUrl = `/users/capture-list/?page=${currentPage}&sort_by=${sortBy}&sort_order=${sortOrder}`; } else { console.error(`Unknown item type: ${this.itemType}`); return; diff --git a/gateway/sds_gateway/templates/base.html b/gateway/sds_gateway/templates/base.html index efa63a094..2e14c130a 100644 --- a/gateway/sds_gateway/templates/base.html +++ b/gateway/sds_gateway/templates/base.html @@ -100,27 +100,38 @@